Android——自定义镂空遮盖控件
2016-05-17 11:11
267 查看
刚学完ViewDragHelper和PorterDuffXferMode的我,突然想做一个这样效果的自定义控件:点击ListView的列表项,通过ViewDragHelper用动画方式上下各弹出一个控件遮盖住ListView,这两个控件在遮盖listView的过程中有一部分是镂空的。先上效果图:
首先是进行页面的布局,让自定义控件PlayLayout继承自Franlayout,在最底层放的就是listView所在的子FramLayout(Id:midContent),然后依次在上面加上下两个看起来被分割的FrameLayout(这里直接写了自定义控件SplitLayout继承Framlayout,id分别为:topSplit,bottomSplit),最后还要放上旋转圆的部分(自定义控件ControlPanel,)
大致布局像这样:
首先分析怎么设置上下SplitLayout弹出的工作:
1、在PlayLayout中的onFinishInflate()中获得各个子布局
2、根据可以设置的splitScale即上下splitLayout高度比(默认0.5,上下各一半),在PlayLayout控件的onMeaure中设置他们的高度
3.重写PlayLayout的onLayout方法,进行子控件的布局
4.通过ViewDragHelper来处理滚动,接下来是ViewDragHelper的一般过程
先初始化
当size发生变化时,重置一些参数
拦截时间,以及处理事件
初始化ViewDragHelper.Callback
还需要重写onComputeScroll(和Scroll.startScrolll方法一样)
ViewDragHelper的惯性操作都放在open和close
其中在ViewDragHelper.Callback的onViewPositionChanged中每次调用我们都计算了当前top,并分发了事件:
5.通过PorterDuffXferMode遮罩层设置镂空的SplitLayout
这里是在SplitLayout控件中
首先初始化
然后重写onDraw时这样处理
旋转圆控件的onDraw处理和SplitLayout基本一样,除了要镂空
注意:要想实现镂空还得,必须在layout:xml布局文件里面指定background如下:
这里也有个问题搞不懂?
当我这这样设置时镂空的地方成了黑色,也不显示下层控件
(android:background=”#00ffffff”)和(android:background=”#ffffff” android:alpha=”0”) 不应该是一样的吗
demo源代码地址(github新手,文件在app module 里面):
https://github.com/yifantao/SkyMusic
首先是进行页面的布局,让自定义控件PlayLayout继承自Franlayout,在最底层放的就是listView所在的子FramLayout(Id:midContent),然后依次在上面加上下两个看起来被分割的FrameLayout(这里直接写了自定义控件SplitLayout继承Framlayout,id分别为:topSplit,bottomSplit),最后还要放上旋转圆的部分(自定义控件ControlPanel,)
大致布局像这样:
<PlayLayout extends frameLayout id:palyLayout> <FrameLayout id:midContent>//显示ListView的底层部分 </FrameLayout> <SplitLayout extends FrameLayout id:topSplit>//顶部的分割区 </SplitLayout> <SplitLayout extends FrameLayout id:bootomSplit>//底部分割 </SplitLayout> <ControlPanel extends FrameLayout id:controlPanel>//旋转圆部分 </ControlPanel> </PlayLayout>
首先分析怎么设置上下SplitLayout弹出的工作:
1、在PlayLayout中的onFinishInflate()中获得各个子布局
midContent = (ViewGroup) getChildAt(0); topSplit = (SplitLayout) getChildAt(1); bottomSplit = (SplitLayout) getChildAt(2); controlPanel = (ControlPanel) getChildAt(3); //SplitLayout.Position是一个enum 标志该SplitLayout是上半部分还是下半部分,默认下半部分 topSplit.setPosition(SplitLayout.Position.TOP);
2、根据可以设置的splitScale即上下splitLayout高度比(默认0.5,上下各一半),在PlayLayout控件的onMeaure中设置他们的高度
int count = getChildCount(); for (int i = 0; i < count; i++) { //测量子控件 measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec); } int w = MeasureSpec.getSize(widthMeasureSpec); int h = MeasureSpec.getSize(heightMeasureSpec); //根据分割比例计算上下分割控件各自的高度 int topH = (int) (h * 1.0f * splitScale + 0.5f); int bottomH = h - topH; ViewGroup.LayoutParams params1 = topSplit.getLayoutParams(); ViewGroup.LayoutParams params2 = bottomSplit.getLayoutParams(); ViewGroup.LayoutParams params3 = controlPanel.getLayoutParams(); params1.height = topH; params1.width = getMeasuredWidth(); params2.height = bottomH; params2.width = getMeasuredWidth(); params3.height = (int) (radias*2); params3.width = (int) (radias*2) ; //设置各自的高度 topSplit.setLayoutParams(params1);//上半部分割控件 bottomSplit.setLayoutParams(params2);//下半部分 controlPanel.setLayoutParams(params3);//旋转圆盘
3.重写PlayLayout的onLayout方法,进行子控件的布局
int h = getMeasuredHeight(); int topH = (int) (h * 1.0f * splitScale + 0.5f); int bottomH = h - topH; //放置listview的底层的midContent正常的layout midContent.layout(left, top, right, bottom); //topSplit一开始我们让其在最上面隐藏,点击listItem后才出来 topSplit.layout(left, -topH, right, 0); //同样,bottomSplit最开始让其在父控件的底部隐藏 bottomSplit.layout(left, h, right, h + bottomH); controlPanel.layout((int)(getMeasuredWidth()*1.0/2-radias), (int)(topH-radias), (int)(getMeasuredWidth()*1.0/2+radias), (int) (topH+ radias));
4.通过ViewDragHelper来处理滚动,接下来是ViewDragHelper的一般过程
先初始化
public enum Status {//一共三个状态,打开,关闭,正在拖动 OPEN, DRAGING, CLOSE } private Status status = Status.CLOSE; private ViewDragHelper mDragHelp; //初始化 private void initView() { mDragHelp = ViewDragHelper.create(this, mDragCallbak); status = Status.CLOSE;//初始化状态,splitLayout和controlPanel处于关闭状态 }
当size发生变化时,重置一些参数
//这里是设置底部的splitLayout可以拖拽 private int mTop;//当前拖拽的位置 private int mStartTop;//初始Y方向top位置 private int mEndTop;//打开后,拖拽到的位置 private int mRange;//可以拖动的范围 @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); int hh = getMeasuredHeight(); int topH = (int) (hh * 1.0f * splitScale + 0.5f); mStartTop = hh;//最开始是在底部 mEndTop = topH;//能够最终拖拽到父控件顶部splitLayout高度大小的位置 mTop = hh;//初始化当前位置 mRange = mStartTop - mEndTop;//计算拖拽范围 }
拦截时间,以及处理事件
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (status == Status.CLOSE) return false; return mDragHelp.shouldInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { mDragHelp.processTouchEvent(event); return true; }
初始化ViewDragHelper.Callback
private ViewDragHelper.Callback mDragCallbak = new ViewDragHelper.Callback() { @Override public boolean tryCaptureView(View child, int pointerId) { return child == bottomSplit;//选择能够拖动的控件 } @Override public int clampViewPositionVertical(View child, int top, int dy) { //设置当前的拖拽位置,要在拖动范围之内 if (top > mStartTop) top = mStartTop; if (top < mEndTop) top = mEndTop; return top; } //当拖动的控件位置发生变化时,调用该函数,在函数里更新当前的top位置mTop @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { super.onViewPositionChanged(changedView, left, top, dx, dy); mTop += dy; mTop = Math.max(Math.min(mTop, mStartTop), mEndTop); dispatchScrollEvent(); } //设置拖动范围 @Override public int getViewVerticalDragRange(View child) { return mRange; } //当拖拽放开时 @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { if (mTop < mEndTop + mRange / 2) { open(); } else { close(); } ViewCompat.postInvalidateOnAnimation(PlayLayout.this); } };
还需要重写onComputeScroll(和Scroll.startScrolll方法一样)
@Override public void computeScroll() { super.computeScroll(); if (mDragHelp.continueSettling(true)) ViewCompat.postInvalidateOnAnimation(this); }
ViewDragHelper的惯性操作都放在open和close
public void open() { mDragHelp.smoothSlideViewTo(bottomSplit, 0, mEndTop); ViewCompat.postInvalidateOnAnimation(this); } private void close() { mDragHelp.smoothSlideViewTo(bottomSplit, 0, mStartTop); ViewCompat.postInvalidateOnAnimation(this); }
其中在ViewDragHelper.Callback的onViewPositionChanged中每次调用我们都计算了当前top,并分发了事件:
private Status updateStatus() { if (mTop <= mEndTop + 5) { status = Status.OPEN; System.out.println("status->>" + "open"); } else if (mTop >= mStartTop - 5) { status = Status.CLOSE; System.out.println("status->>" + "close"); } else { status = Status.DRAGING; } return status; } private void dispatchScrollEvent() { updateStatus();//根据当前mTop计算状态 //计算拖拽完成的程度 float percent = (mStartTop - mTop) * 1.0f / mRange; //根据百分比,移动呈现顶部splitLayout ViewHelper.setTranslationY(topSplit, percent * 1.0f * mEndTop + 0.5f); ViewHelper.setScaleY(controlPanel, percent); ViewHelper.setScaleX(controlPanel, percent); ViewHelper.setRotation(controlPanel, (1 - percent) *360); }
5.通过PorterDuffXferMode遮罩层设置镂空的SplitLayout
这里是在SplitLayout控件中
首先初始化
private PorterDuffXfermode mMode; private Paint mPaint; private Bitmap mBg;//控件背景 private void initView() { mPaint = new Paint(); mPaint.setAlpha(0);//镂空遮罩层画笔一定要透明 mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); }
然后重写onDraw时这样处理
int w = getMeasuredWidth(); int h = getMeasuredHeight(); Bitmap bm = null; if (mBg != null) { int bitW = mBg.getWidth(); int bitH = mBg.getHeight(); //因为该控件只显示父控件一定比例的高度,所以背景比例是按父控件高度计算的 float scaleH = h * 1.0f / splitScale / bitH;//h*(1/splitScale)得到父控件高度 float scaleW = w * 1.0f / bitW; Matrix matrix = new Matrix(); matrix.setScale(scaleW, scaleH); bm = Bitmap.createBitmap(mBg, 0, 0, bitW, bitH, matrix, true); } int startDegree = mPosition == Position.TOP ? 180 : 0; //利用同样大小的bitMap做镂空层 Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); Canvas canvas1 = new Canvas(bitmap); //根据Position决定绘制已经缩放图像的上半还是下半,在下半则起点为父控件高度减去自己的高度 int t = mPosition == Position.TOP ? 0 : (int) (h * 1.0f / splitScale - h); if (mBg == null) canvas1.drawColor(Color.argb(200, 128, 128, 128)); else canvas1.drawBitmap(bm, new Rect(0, t, w, t + h), new Rect(0, 0, w, h), null); //设置镂空圆的位置 float top = h - radias; if (mPosition == Position.BOTTOM) top = -radias; float bottom = top + 2 * radias; RectF rectF = new RectF(w / 2 - radias, top, w / 2 + radias, bottom); //上面再bitmap上已经画了背景图片,再通过带xferMode属性的透明画笔去镂空背景图片,让其显示控件下面一层的控件视图 canvas1.drawArc(rectF, startDegree, 180, true, mPaint);//注意mPaint的特殊设置,alpha=0,xmode(Mode.SRC_In); //将镂空层画到背景上 canvas.drawBitmap(bitmap, 0, 0, null);
旋转圆控件的onDraw处理和SplitLayout基本一样,除了要镂空
int bitW = mBg.getWidth(); int bitH = mBg.getHeight(); System.out.println("sout->>bitH=" + bitH + " bitW=" + bitW); //计算缩放比例 float scaleH = h * 1.0f / bitH; float scaleW = w * 1.0f / bitW; Matrix matrix = new Matrix(); matrix.setScale(scaleW, scaleH); Bitmap bitmap=Bitmap.createBitmap(mBg,0,0,bitW,bitH,matrix,true); mShader=new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);//通过BitmapShader很容易自定义各种什么圆角,圆形背景控件 mPaint.setShader(mShader); canvas.drawCircle(w/2,h/2,radias,mPaint);
注意:要想实现镂空还得,必须在layout:xml布局文件里面指定background如下:
<com.fan.skymusic.SplitLayout android:background="#00ffffff" android:id="@+id/topSplit" android:layout_width="match_parent" android:layout_height="wrap_content"> </com.fan.skymusic.SplitLayout>
这里也有个问题搞不懂?
当我这这样设置时镂空的地方成了黑色,也不显示下层控件
<com.fan.skymusic.SplitLayout android:background="#ffffff" android:alpha="0" android:id="@+id/topSplit" android:layout_width="match_parent" android:layout_height="wrap_content"> </com.fan.skymusic.SplitLayout>
(android:background=”#00ffffff”)和(android:background=”#ffffff” android:alpha=”0”) 不应该是一样的吗
demo源代码地址(github新手,文件在app module 里面):
https://github.com/yifantao/SkyMusic
相关文章推荐
- Android 使保存的图片在图库中显示
- android 命令
- Android studio出现 Rendering Problem问题解决方案
- android从零开始播放视频
- Android Studio 注释模板
- Android原生的刷新控件--SwipefreshLayout
- Android性能常用的测试工具(黑盒)
- Android用户图片上传功能的实现
- Android中使用WIFI来连接ADB
- Android版:验证手机号码的正则表达式
- android 看门狗
- Android greendao配置及使用
- Android sdk更新,源码下载等
- Android NDK 版本说明
- android gradle编译问题整理贴s
- Android文件Apk下载变ZIP压缩包解决方案
- Android之免费短信验证Mob
- Android 传感器的使用
- android 系统权限说明
- Android概念