1 快速索引 细节问题:
1.1 把当前被选中的字母索引置为灰色,否则为白色
每一次在快速索引栏上的触摸事件都触发invalidate(),重走onDraw()方法
在onDraw()方法里,做判断,如果通过触摸事件计算的索引与绘制字母数组的索引一致时就更改画笔的颜色,(记得在触摸事件中如果手指抬起,就把计算的索引置为-1)
1.2 弹出吐司不太好看,弹出一个圆角的矩形框会好看一些(实际上就是一个圆角的TextView,平常隐藏,滑动的时候显示)
圆角:定义背景的xml文件,shape根节点,弧度属性节点设置即可
调用者使用监听器的时候,获取到当前选中的索引,传递到TextView上,并让它显示出来,延迟两秒之后让它消失即可(使用handler.postDalyed(Runnable,time)).
问题1:延迟两秒之后消失,在没消失的时候,索引更换了,在上一个两秒到达,TextView就消失了,需要在每次获取到索引之后,移除之前的延迟操作和消失
handler.removeCallbacksAndMessages(null).
2,视差特效:下拉界面的时候,费劲的拉动才出现图片,如:QQ空间,微信朋友圈,背景
实现步骤:可以重写ListView overScrollBy方法(自定义ListView)
2.1 填充数据,方便观察
2.2 创建自定义ListView ,重写构造方法
调用者可以直接获取到对象后进行addHeaderView(view)
创建头布局文件(需要有视差特效的内容)
如果是图片:imageView的scaleType属性:centerCrop值(等比例进行缩放,会自动适配,根据不同需求来设置不同的值即可)
设置了centerCrop之后,动态的修改图片的高就可以看到图片的范围内容
2.3 下拉放大的效果
重写ListView的overScrollBy(int delateX,int,delateY,int scrollX,int scrollY,int scrollRangX, int scrollRangY,int maxOverScrollX,int maxOverScrollY,boolean isTouchEvent)
①参数解释
delateY: 竖直方向滑动的瞬时变化量,顶部下拉为负,底部上拉为正数
scrollY: 两端的滑动距离,顶部为负,底部为+
scrollRangY: 竖直滑动的范围
maxOverScrollY: 竖直方向的最大滑动位置(绝对值)
isTouchEvent: 手指拉的为true,滑动惯性为false
overScrollBy方法是在滑动到ListView两端才会被调用
②根据需求,限定事件的触发范围
delateY<0,并且是手指滑动的isTouchEvent.
在自定义控件类中,创建一个方法让调用者可以设置头布局的图片(因为调用者直接设置addHeaderView,自定义控件类中获取不到HeaderView的)
setParllaxImage(图片控件)//但是在控件类中,拿不到控件的宽高
//在调用者类中,等待View的树状结构全部渲染完毕后,再去setParXX,设置到控件里
Iv_header.getViewTreeObserver().addOnGloballLayoutListener(new OnGloBallLaoutListener(){})
然后再进行setParXXXX(图片控件)
然后再把这个回调给移除掉removeGlodBallXXX(this)//不然每次渲染都走这里
在自定义控件的overScrollBy()方法里事件触发范围,动态的修改这个图片的高
Int newHeight = iv_header.getHeight() + Math.abs(deltaY)(因为deltaY是负数)
//让新的值生效
iv_header.getLayoutParams().height = newHeight;
iv_header.requestLayout();//改了值之后,要让它重新生效
③获取到图片的原始高度,设置范围
Iv_header.getDrawable().getIntrinsicHeight();//获得图片的原始高度
如果新的高度大于原始高度,就不再增加高度了
2.4松开回弹动画
①重写onTouchEvent()方法,不要删了super.onTouchEvent()它做了很多判断
当手指松开的时候,获取到图片当前的高
int currentHeight = iv_header.getHeight();
ValueAnimator(值动画,它是ObjectAnimator父类) animator = ValueAnimator.ofInt(开始,结束)
animator.addUpdateListener(new AnimatorUpdateListener(){
重写的方法里,通过参数animation
Float fraction = animation.getAnimatedFraction();//获取动画执行的分度值(进度)
Int animatedValue= (int)animation.getAnimatedValue();//获取中间的值
})
animator.setDuration(500)//设置播放时间
animator.start()//开始播放
这样就可以在重写的方法里,动态的设置头图片的值
中间执行的数值可以通过typeEvaluator类型布局器计算,这个值动画底层已经做了
②希望松开的时候回弹一下
animator.setInterpolator(new OvershootInterpolator(2))//设置动画的插补器
OverXxXpolator()//超出射击的插补器,参数值越大,弹的范围越大,只弹一次
③拖拽图片的delateY/2,或除以3都可以,让拖拽显得比较费劲
3,侧滑删除(应用场景,QQ聊天信息,邮件管理等,需要对条目功能进行拓展)
参考ui
这里的尾巴是跟着一起出来的,更高端
3.1 实现步骤:同侧滑面板
3.2 每一个子条目都是一个容器,继承FrameLayout的自定义控件
①重写三个构造.
②创建ViewDragHelper(容器(this),敏感度,回调callback) mHelper
③转交触摸事件拦截判断,处理触摸事件
onInterceptTouchEvent()中进行拦截判断mHelper.shouldXXXX();
onTouchEvent()中mHelper.processTouchEvent(event)//tryCatch可以处理掉多点触摸的BUG
④重写回调的方法
3.3 界面的初始化
在这个自定义控件容器下有两个子容器LinearLayout(前布局,后布局)
设置好它们的布局即可
3.3.1初步实现:
ViewDragHelper只能对孩子生效,不能对孙子生效
在回调的方法里:
tryCaptureView()里 返回true,对所有孩子生效
clampViewPositionHorizontal()返回left建议移动的位置即可,返回值决定将要移动到的位置
3.4 拖拽事件的传递
3.4.1 在onFinishInflate()中找到两个子孩子
在onSizeChanged()//获取孩子的到宽(高不需要)
mBackView.getMeasuredWidth()//获取到宽
3.4.2 回调方法里
clampViewPositionHorizontal()限制范围 ,
前布局移动范围在-backViewWidth>>0之间
后局部移动范围在wWidth - backViewWidth>>wWidth之间
3,4.3 摆放布局位置,后布局应该在屏幕的右边之外
在onLayout()布局方法里,创建方法layoutContent(false)//摆放内容,默认关闭
①设置前布局位置:
Rect rect = computeFrontRect(isOpen)//抽取一个方法,计算出前布局的矩形位置
//如果是关闭状态
int left = 0;
如果打开状态就是mRange(就是-backViewWidth)
返回一个矩形 new Rect(left,0,left + mWidth + ,0 + mHeight);
然后再mFrontView(前布局).layoutXXXX(矩形参数)
这样写的话,各种复杂的业务逻辑都能计算,方便拓展
②后布局位置,在前布局的尾巴后,抽取方法名,参考computeBackRectViaFront(rect)
计算后布局的区域范围
返回一个矩形 new Rect(rect.left,0,left+mRange,0 + mHeight);
③更改布局顺序,调整显示效果(后布局覆盖前布局)
bringChildToFront(view)//把任意布局顺序调整到最上面
④希望在拖拽的时候,动态的修改两个布局位置
在回调方法里onViewPositionChanged()当位置变化时,把水平变化量传递给另一个布局
把发生的dx(偏移量)传递给另一个布局即可
可以写(布局).layout();
不过这里用(另一个布局).offsetLeftAndRight(偏移量);
//兼容低版本
Invalidate()//刷新数据
3.5 松手的时候平滑动画
回调方法 onViewReleased()//松开的时候被调用
xvel//水平方向加速度,yvel//竖直方向的加速度
判断:
如果水平方向的加速度等于0,并且前布局的位置小于-mRange*0.5f(后布局的一半)就open();
如果水平方向加速度大于0就close();小于0 open();
这时候就可以调用前面的layoutContent(boolean)方法
不过直接调用没有动画,所以还要执行一个平滑动画
判断是否是平滑,否则直接执行layoutContent(boolean)方法跳过去
如果是,开启一个平滑动画
判断触发平滑动画mHelper.smoothSlidView(控件,最终left,botton)
//重绘界面,invalidate()方法会有丢帧的情况
返回为true>>>ViewCompat.postInvalidateOnAnimation(this)
维持动画继续,重写computScroll()方法,执行下一帧
在这个方法里
mHelper.continueSetting(this)//返回的是true,代表动画还未执行完
ViewCompat.postInvalidateOnAnimation(this)//维持平滑动画的继续
3.6 监听回调
状态分析:三种,打开,关闭,滑动
使用一个枚举表示状态Close,Open,Swiping
默认为Close
监听回调.....
调用者不需要关心中间滑动的过程
所以对外的监听回调,只需要Close,Open,方法即可
所以还需要定义一个监听器方法
onStartOpen()方法和onStartClose()方法//
状态更新在回调的onViewPositionChanged()方法里调用做分发状态
更新当前的状态,参考方法名:dispatchDragEvent()
Status = updateStatus()//用一个方法去判断
在updateStatus()里,判断前界面.left的位置
//获取完最新状态,与老状态做比较
如果不一致,就更新老状态,并调用接口的方法.
当新状态是滑动状态,并且与老状态不一致,根据老状态的不同,
如果是开启,就调用onstartOpen()方法,否则onStartClose();//代表正在要打开或关闭
3.7 添加到ListView里
当控件被当做条目添加到ListView里,根节点的宽高会被改成适应ListView,解决方法
可以在子节点里设置宽高,或者直接在根节点里指定mineHeight最小的高度.
为在ListVIew进行判断当前滑动的条目,所以在创建回调方法时,把this(当前滑动对象)传递进去.
调用者在适配器类中,记录下每一个被打开的条目,如果关闭了就移除掉.
调用者可以在onStartOpen()中,关闭掉这个集合中所有的对象,遍历关闭
4.粘性控件(主要学习自定义View):
应用场景:未读提醒 参考效果:
4.1 ①绘制一帧的界面效果(固定值)
画一个拖拽圆,和中间的连接部分,以及固定圆
创建一个类GooView继承自View,在onDraw()方法里进行绘制
//画一个圆
canvas.drawCircle(x,y,半径,画笔)
Paint = new Paint(Paint.ANTI_ALIAS_FLAG);//设置抗锯齿
paint.setColor();//设置颜色
//画曲线:cavas没有直接画曲线的方法,但是可以画一条路径
参考分析图
cavas.drawPath(path,paint)
Path path = new Path();
//跳到点1开始画
path.moveTo(x,y);
//从点1到点2画曲线 贝塞尔曲线算法
path.quaTo(cx,cy,ex,ey)//二阶曲线,执行这个方法如果没有调用moveTo()就从0.0开始
前两个坐标是控制点的坐标(此案例的控制点就是沙漏的中心点)
Path.close();//自动封闭,两点之间的直线与曲线之间的区域进行填充
//从点2到点3画直线
Path.linto(x,y)//这两个点属于直线
//点3到点4
Path.quadTo( cx,cy,ex,ey);
//点4到点1可以不画,因为已经填充完了
②让固定值变成变量
PointF()//点的封装类,封装了一个点的X,Y坐标
把两个圆的固定属性替换成变量.
找到固定圆的两个附着点(点1,4)坐标,这两个个点封装到一个PointF()的数组里,
找到拖拽圆的两个附着点(点2,3)坐标,这两个个点封装到一个PointF()的数组里
然后画路径
Path.XXX方法的参数用这些封装点对象的值进行替换
③计算变量
计算四个附着点坐标,(圆的坐标一个是固定,一个是根据触摸事件设置)
GeometryUtil几何学工具类
GeometryUtil.getIntersectionPoints(圆心坐标,半径,角度linek)(返回一条直线的)
lineK=两个圆心Y轴的差值/X轴的差值//如果X差值为0,就为null即可,工具类有对策
一个控制点的坐标
GeometryUtil.getMiddlePoint(圆1,圆2)//获取两个圆之间的中间点
画出附着点(参考使用)(方便观察,画四个附着点小圆点)
4.2 根据触摸事件动态绘制
观察可知,随着拖拽圆与固定圆的圆心距离越远,固定圆半径越小
触摸事件中
①按下的时候getRawX()//如果getX()是不能超过父控件的区域,所以用getRawX()屏幕坐标
更新圆心的坐标mDragCenter.set(x,y)//pointF封装的方法
updateDragCenter(x,y)//更新坐标
如果发现坐标错位,把标题去掉
还是有细微的错位,因为还有状态栏的问题
可以把状态栏去掉,或者获取X,Y之类,这里把画布向上平移画布,就可以把坐标修正
Canvas.translate(x,y)//画布平移
②获取状态栏高度onSizeChanged()里
Utils.getStatusBarHeight(View)//传入谁的View都可以,
//获取屏幕可视的矩形框(不包含标题栏)
实际上是使用了view.getWindowVisibleDisplayFrame(frame(Rect))
返回frame.top就能得到可用屏幕的头坐标
③Canvas.save()//保存当前画布位置状态
Canvas.translate(0,-frame.top);
Canvas.restore()//恢复画布移动之前,画的小圆点位置还是在移动之后的.
④根据两个圆心的距离,计算出固定圆的半径
创建一个方法,在里面通过工具类GemotryUtil.getXXXXX2Point(圆心1,圆心2);
返回一个float数,表示两个圆心之间的距离
//可以定义一个最远距离,80f比较合理
通过类型估值器,计算从固定圆12f的半径>>>3f的半径
比值:圆心的距离/最大距离,如果超过最大距离,就一直等于80
然后把算出来的半径值传递回来,更改方法里面的值:固定圆半径,和附着点的坐标
类型估值器中,矩形估值器作用:在选择的时候传入开始矩形和最后一个矩形,可以获取到中间的所有矩形框(机顶盒应用中通过遥控器选中项目时,上下左右选中项目的时候,每次跳一个矩形框,这个框的位置就是这样算出来的)
4.3 事件处理.
4.3.1 画出最大范围(圆环,参考使用)
paint.setStyle(Style.STROKE);//填充边线
canvas.drawCircle(固定圆心.x,固定圆,半径>>最大范围(80),画笔paint);
paint.setStyle(Style.FILL);//恢复原样
4,3,2 事件1:当拖拽圆的超出最大范围,不再绘制连接曲线和固定圆
移动的时候
Float d = 通过工具类获取两个圆之间的距离
if(d>最大范围){
isOutofRange = true;//是否超出最大范围
刷新数据
}
在绘制的方法里,做判断
如果超出最大范围就不再绘制连接曲线和固定圆
按下的时候
重置为isOutofRang = false;
isDisappear = false;
事件2:松手的时候
根据isOutofRang来判断,
如果为true,
分支:在范围外:
再获取一次两个圆的距离,如果还是大于最大范围,就更改一个变量
Boolean isDisappear = true;
然后刷新数据.
绘制的方法里,如果为true,就不绘制所有控件
如果小于最大范围,重设一下拖拽圆的范围,修正为固定圆的圆心位置
如果为false
分支:在范围内松手,播放一个动画,恢复原来
ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
animator.addUpdateListener(new XXXListener){
重写的方法中,参数animation
animation.getAnimatedFraction();//获取动画的进度,相当于获取到了一切!
获取到了进度,就可以设置拖拽圆的动画
在外面先获取到拖拽圆的圆心PointF startP = new PointF(x,y);
GeometryUti.getPointByPercent(startP,mStichCenter(固定圆),进度)//返回个点
让这个点生效
更新数据
updateDragCenter(x,y)//这是自己写的方法
}
animator.setDuration(500);
//创建一个插补器,让拖拽圆弹一下,用户体验更好
animator.setInterpolator(new OverShootInterpolator(4));
Animator.start();
4.4 事件的监听回调
创建一个接口提供方法
onReset(boolean)恢复后调用(超出范围恢复true,和没超出范围恢复)
onDisapper()//消失后调用
在松开事件里根据事件类型调用方法
在范围内松开之后,动画播放完毕才调用onReset(false)
动画结束的监听:animator.addListener(new AdimatorListenerAdater(){
重写动画播放完毕的方法,不用强制重写所有方法,在这里面调用onReset(false)方法
})
4.5 小细节:
拖拽控件与ListView的结合,小红点本身是个TextView
点击这个TextView,生成一个WindowManager,然后在WindowManager上生成一个全屏的透明的控件(TextView可以),自定义View添加到上面去,就可以全屏移动了
ViewParent parent = getParent();//获取父容器
然后把TextVie的触摸事件传递给自定义View gooView
parent.requestDisallowInterceptTouchEvent(true)//请求父类不拦截touch事件
当恢复的时候,通过WindowManager把自己给移除掉
画文本,就是画完拖拽圆之后,画一个同样坐标的Text(再给它单独搞个画笔,设置大小,设置颜色,居中对齐方式)
5,快捷键