今天朋友需要一个图片滑动展示的控件,要求图片折叠时除第一张图片,其他都只保留底部一部分可见,可上下滑动展示。
先讲讲我的思路,本来想用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,由于需要对滑动后的控件大小进行改变,我这边用的最后一张图片顶部高度来计算控件高度,效果是一致的。
这是我对滑动后控件大小进行改变的操作。
完成控件大小的设置,就要来绘制图片了,这个我们在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://按下 down_y = (int) event.getY(); break; case MotionEvent.ACTION_UP://抬起 oldy = 0; break; case MotionEvent.ACTION_MOVE://移动 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 < infoList.size(); 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自定义控件及事件分发机制的知识。
这边是源码:https://github.com/flychen11/MySlideImageView.git