首先声明本人Android新人一枚,这是本人第一篇博客,用于对自己知识的巩固,同时把自己写的东西分享出来,希望能帮到有需要的人。
今天朋友需要一个图片滑动展示的控件,要求图片折叠时除第一张图片,其他都只保留底部一部分可见,可上下滑动展示。
先讲讲我的思路,本来想用RecyclerView配合隐藏图片来完成的,可是后来一想这样适配性太差,只能用于已知图片数量的情况,于是我决定写一个自定义控件来完成这个需求。这个控件里面最重要的就是控件的大小,绘制图片,控件中图片滑动响应,处理父控件滑动事件的屏蔽。
首先控大小,我们通过重写onMeasure方法来完成,
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (infoList.size() ==0) {
setMeasuredDimension(pWidth, bHeight);//设置控件宽高
}else {
setMeasuredDimension(pWidth, bHeight +infoList.get(infoList.size() -1).hightTop);//设置控件宽高
}
}
宽度直接是使用的屏幕宽度,高度我设置了一个默认图片,以这个默认图片的高度bHeight来计算,每多一张图片高度就增加bHeight/4,由于需要对滑动后的控件大小进行改变,我这边用的最后一张图片顶部高度来计算控件高度,效果是一致的。
ViewGroup.LayoutParams layoutParams =this.getLayoutParams();
layoutParams.height =bHeight +infoList.get(infoList.size() -1).hightTop;
this.setLayoutParams(layoutParams);
invalidate();
这是我对滑动后控件大小进行改变的操作。
完成控件大小的设置,就要来绘制图片了,这个我们在onDraw中进行处理,
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Rect srcRect =new Rect(0, 0, pWidth, bHeight);//要绘制的bitmap 区域
if (list.size() ==0) {
Rect destRect =new Rect(0, 0, pWidth, bHeight);//要将bitmap 绘制在屏幕的什么地方
canvas.drawBitmap(bitmap, srcRect, destRect, paint);
}else {
for (int i =list.size() -1; i >=0; i--) {
BitmapInfo bitmapInfo =infoList.get(i);
Rect destRect =new Rect(bitmapInfo.widthLeft, bitmapInfo.hightTop, bitmapInfo.widthRight, bitmapInfo.hightTop +bHeight);//要将bitmap 绘制在屏幕的什么地方
canvas.drawBitmap(list.get(i), srcRect, destRect, paint);
}
}
}
接下来就要处理控件中图片滑动的响应了,首先重写onTouchEvent方法来对手势进行监听,
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN://按下
getParent().requestDisallowInterceptTouchEvent(true);//屏蔽父类的事件屏蔽
down_y = (int) event.getY();
break;
case MotionEvent.ACTION_UP://抬起
oldy =0;
break;
case MotionEvent.ACTION_MOVE://移动
getParent().requestDisallowInterceptTouchEvent(true);//屏蔽父类的事件屏蔽
moveImageView((int) (event.getY() -down_y));
break;
}
return true;
}
分别对手指按下,抬起,移动进行了处理,按下时获取手指坐标,移动时获取坐标,计算出移动距离,并对图片进行移动,最后返回true,表示在手势完成后,不会再发送给父类进行处理了。
private void moveImageView(int y) {
if (infoList.size() ==0) {
return;
}
if (y <0) {//向上滑动
for (int i =0; i
BitmapInfo bitmapInfo =infoList.get(i);
if (i ==0) {
if (bitmapInfo.hightTop > (-infoList.size() +1) *bHeight) {
bitmapInfo.setHightTop(bitmapInfo.hightTop + y -oldy);
if (bitmapInfo.hightTop < (-infoList.size() +1) *bHeight) {
bitmapInfo.setHightTop((-infoList.size() +1) *bHeight);
}
}
}else {
BitmapInfo lastBitmapInfo =infoList.get(i -1);
if (bitmapInfo.hightTop > (-infoList.size() + i +1) *bHeight && lastBitmapInfo.hightTop <= -(4 - i) *bHeight /4) {
bitmapInfo.setHightTop(bitmapInfo.hightTop + y -oldy);
if (bitmapInfo.hightTop < (-infoList.size() + i +1) *bHeight) {
bitmapInfo.setHightTop((-infoList.size() + i +1) *bHeight);
}
}
}
}
}else {//向下滑动
for (int i =list.size() -1; i >=0; i--) {
BitmapInfo bitmapInfo =infoList.get(i);
if (bitmapInfo.hightTop < i *bHeight /4) {
bitmapInfo.setHightTop(bitmapInfo.hightTop + y -oldy);
if (bitmapInfo.hightTop > i *bHeight /4) {
bitmapInfo.setHightTop(i *bHeight /4);
}
}
}
}
oldy = y;
ViewGroup.LayoutParams layoutParams =this.getLayoutParams();
layoutParams.height =bHeight +infoList.get(infoList.size() -1).hightTop;
this.setLayoutParams(layoutParams);
invalidate();
}
由于在移动图片是会用到上一步移动的距离oldy,所以在抬起手指的手势处理中多oldy进行初始化,为了更好的处理图片移动时,位置的变化,我用了一个内部bean类来记录图片的位置信息,
private class BitmapInfo {
private int widthLeft;
private int hightTop;
private int widthRight;
private int hightBottom;
BitmapInfo(int widthLeft, int hightTop, int widthRight, int hightBottom) {
this.widthLeft = widthLeft;
this.hightTop = hightTop;
this.widthRight = widthRight;
this.hightBottom = hightBottom;
}
void setHightTop(int hightTop) {
this.hightTop = hightTop;
}
}
到这里控件其实已经完成了,不过在我们把控件放到ScrollView中时,我们会发现我们的图片滑动不了,我们的手指触摸事件被ScrollView屏蔽了,这里我们就要在对应的触摸事件中队父控件的屏蔽事件进行屏蔽,
getParent().requestDisallowInterceptTouchEvent(true);//屏蔽父类的事件屏蔽
,到这里整个控件就大功告成了。
总结:这是一个很简单的控件,没有花哨的效果,这里面会用到Android自定义控件及事件分发机制的知识。