自定义View技巧
来源:互联网 发布:如何强身健体知乎 编辑:程序博客网 时间:2024/06/05 15:01
这篇博客会记录自定义View中几个技巧,帮助更好,更快实现自定义View
灵活使用 save() restore()
- save:用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等
- restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响
举个例子:如果你要画钟表如下图并在长刻度线外边画上1-12数字:
共60个刻度线,每四个刻度线之后就是一个长刻度线,并在长刻度线外边画上对应数字。
两种实现思路:
- 在不使用save() rotate()的情况下,每个刻度线间隔为6度,先求的12点的坐标然后根据正弦,余弦 求得每个刻度线向下两点坐标,最后使用drawLine()即可。
- 在onDraw()中使用restore() 和 save() 相结合具体代码如下
int width = getMeasuredWidth(); int height = getMeasuredHeight(); String number; logLine = width / 16f; //长刻度线长度 shortLine = logLine / 2; //短刻度线长度是长刻度线一半 space = logLine / 5;//长刻度线和数字之间的间距 canvas.save();//先保存当前canvas的状态 for (int i = 0; i < 60; i++) { number = (i == 0) ? "12" : String.valueOf(i / 5); if (i % 5 == 0) { //长刻度线 mLongPaint.getTextBounds(number, 0, number.length(), mNumberRect); canvas.drawText(number, getWidth() / 2 - mNumberRect.width() / 2 - longStrokeWidth / 2, mNumberRect.height(), mLongPaint); canvas.drawLine(width / 2, mNumberRect.height() + space, width / 2, logLine + space + mNumberRect.height(), mLongPaint); } else { //短刻度线 canvas.drawLine(width / 2, mNumberRect.height() + space + logLine - shortLine, width / 2, mNumberRect.height() + space + logLine, mShortPaint); } canvas.rotate(6f, width / 2, height / 2); //每次画完就旋转6度 } canvas.restore();//恢复canvas状态
这就实现了上图全部效果,可以看到第二种方法要比第一种方法简单许多,我们不需要考虑进行复杂的数学公式计算。所以在实现这种类似效果应该优先选择save(),restore() 方法。
如果你对canvas 相关api 不太了解可参考一下链接:
http://blog.csdn.net/wning1/article/details/60156333(canvas draw方法及效果展示)
http://blog.csdn.net/harvic880925/article/details/39080931(作者对roate() translate() scale()等方法原理解释的非常透彻)
NestScrolling实现嵌套滑动
NestScrolling 设计专门用于解决嵌套滑动,涉及到类包括 NestedScrollingChild , NestedScrollingChildHelper ,NestedScrollingParent,NestedScrollingParentHelper
下面效果图中列表使用RecyclerView实现,可以看到在head软件介绍没有完全隐藏之前RecyclerView 是不允许滑动的。
本效果参考示例:https://github.com/hongyangAndroid/Android-StickyNavLayout (hongyang大神)
最外层StickyNavLayout继承 LinearLayout,这样我们可以很方便的利用纵向布局的特性,对Top,Tabs,已经下面的RecyclerView进行纵向排序。
两种实现思路:
- 常见方法复写dispatchTouchEvent() ,onInterceptTouchEvent(), onTouchEvent()方法进行条件拦截处理。
使用NestScrolling 机制进行处理。
第一种实现方法:
需要外部滑动有两种情况:(1)Top没有完全隐藏,这个时候优先滑动Top直到Top被完全隐藏掉,才能滑动RecyclerView中的内容。(2)Top已经被完全隐藏,RecyclerView中getScrollY() == 0 同时向下进行拖动,这个时候应该逐渐显示Top。这两种情况都应该是onInterceptTouchEvent中进行判断,满足上面两种情况就返回true,然后交给自己的onTouchEvent进行处理,如果不不满足的话就传递给下一层的RecyclerView.
值得注意的一点问题:父布局onInterceptTouchEvent 一旦返回true就不会再进行onInterceptTouchEvent 方法判断了,会直接执行自己的onTouchEvent方法。不能执行onInterceptTouchEvent 那不满足的情况怎么传递给子View呢,这样岂不是事件永远传递不到子View中了?根据这个问题hongyang大神采用了如下方法处理
在 StickNavLayout中的onTouchEvent方法中进行判断。
public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); float y = event.getY(); switch (action) { case MotionEvent.ACTION_DOWN: return true; case MotionEvent.ACTION_MOVE: float dy = y - mLastY; if (!mDragging && Math.abs(dy) > mTouchSlop) { mDragging = true; } if (mDragging) { scrollBy(0, (int) -dy); // 如果topView隐藏,且上滑动时,则改变当前事件为ACTION_DOWN if (getScrollY() == mTopViewHeight && dy < 0) { event.setAction(MotionEvent.ACTION_DOWN); dispatchTouchEvent(event); isInControl = false; } // 如果topView隐藏,且上滑动时,则改变当前事件为ACTION_DOWN if (getScrollY() == mTopViewHeight && dy < 0) { event.setAction(MotionEvent.ACTION_DOWN); dispatchTouchEvent(event); isInControl = false; } } mLastY = y; break; case MotionEvent.ACTION_CANCEL: mDragging = false; break; case MotionEvent.ACTION_UP: mDragging = false; } return super.onTouchEvent(event); }
在ACTION_MOVE执行体中,还是进行条件判断,如果不满足则事件设置为Down,比并交给dispatchTouchEvent,重新走事件流程,就会再次判断onInterceptTouchEvent 不满足情况就交给子View处理。
第二种实现方法:
使用NestScrolling 实现,如前面所说 NestedScrollingChild , NestedScrollingChildHelper 用在子View中,NestedScrollingParent,NestedScrollingParentHelper 用在父View中继承。
NestedScrollingParent(interface)主要包含一下方法:
//该方法决定了当前控件是否能接收到其内部View(非并非是直接子View)滑动时的参数;假设你只涉及到纵向滑动,这里可以根据nestedScrollAxes这个参数,进行纵向判断。满足消费条件返回truepublic boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);//onStartNestedScroll之后调用,可在此方法中做一些初始化操作public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);//该方法的会传入内部View移动的dx,dy,如果你需要消耗一定的dx,dy,就通过最后一个参数consumed进行指定,例如我要消耗一半的dy,就可以写consumed[1]=dy/2public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed);//onNestedPreFling你可以捕获对内部View的fling事件,如果return true则表示拦截掉内部View的事件。public boolean onNestedPreFling(View target, float velocityX, float velocityY);public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);public int getNestedScrollAxes();
NestScrollingChild(interface)
//设置true支持移动嵌套public void setNestedScrollingEnabled(boolean enabled);public boolean isNestedScrollingEnabled();//NestedScrollingChildHelper startNestedScrollpublic boolean startNestedScroll(int axes);public boolean hasNestedScrollingParent();public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);//如果父类(并不一定是直接父类)中有继承NestScrollParent的,则该方法最终会调 NestScrollParent 中的onNestedPreScrollpublic boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);public boolean dispatchNestedPreFling(float velocityX, float velocityY);
至于使用例子可以参考RecyclerView,RecyclerView 直接继承了NestScrollChild,随便复制两个方法:
public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { // Re-set whether nested scrolling is enabled so that it is set on all API levels setNestedScrollingEnabled(nestedScrollingEnabled); } @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); } @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed); } private NestedScrollingChildHelper getScrollingChildHelper() { if (mScrollingChildHelper == null) { mScrollingChildHelper = new NestedScrollingChildHelper(this); } return mScrollingChildHelper; }
可以看到直接都是调用的ChildHeplper的方法,实际上NestScrollChild的所有的方法的实现都是通过NestChildHelper实现的,系统已经给我们这个帮助类实在是十分方便。
那么现在要实现上图的效果StickNavLayout只需要这些代码
public class StickyNavLayout extends LinearLayout implements NestedScrollingParent{ @Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; } @Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { boolean hiddenTop = dy > 0 && getScrollY() < mTopViewHeight; boolean showTop = dy < 0 && getScrollY() > 0 && !ViewCompat.canScrollVertically(target, -1); if (hiddenTop || showTop) { scrollBy(0, dy); consumed[1] = dy; } } @Override public boolean onNestedPreFling(View target, float velocityX, float velocityY) { if (getScrollY() >= mTopViewHeight) return false; fling((int) velocityY); return true; }}
解决嵌套问题就变得so easy !
Scroller 和 OverScroller
Scroller主要用于从一个位置移动到另外一个位置,OverScroller则是fling的相关处理。
Scroller
public Scroller(Context context) {}//可以给Scroller设置一个插值器这样就可以有特殊运行轨迹了,默认情况下是ViscousFluidInterpolator(粘性流体插值器)public Scroller(Context context, Interpolator interpolator){}public void startScroll(int startX, int startY, int dx, int dy, int duration) {}
标准代码示例:
public void smoothScrollTo() { mScroller.startScroll(currentPoint.x, currentPoint.y, -gestureListener.xDis, -gestureListener.yDis, 1000); invalidate(); } //必须复写 @Override public void computeScroll() { super.computeScroll(); if (mScroller != null) { if (mScroller.computeScrollOffset()) { ScrollTo(mScroller.getCurrX(),mScroller.getCurrY()); //自定义操作 postInvalidate(); } } }//stoppublic void stop(){ if (!mScroller.isFinished()) { mScroller.abortAnimation(); }}
通过调用smoothScrollTo()中startScroll() invalidate()通知onDraw()重新执行,而在onDraw()中又会调用computeScroll(),这样就形成了一个循环,直到运动到终止点。
OverScroller
fling 用户手指快速滑动离开屏幕到View停止这段时间段为Fling 状态。
//VelocityX 单位时间内水平方向移动像素点,可以为负值,计算方式(终止点-起始点)/时间段 public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) {}
在调用fling 同样需要复写computeScroll() ,代码和上面一样就不再写了。
那么VelocityY 和 VelocityX这两个参数应该怎么获取呢? 使用VelocityTracker即可获取 api
mVelocityTracker = VelocityTracker.obtain(); //初始化mVelocityTracker.addMovement(ev);mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);//必选先调用该方法下面才能获取水平和纵向速度int velocityY = (int) mVelocityTracker.getYVelocity();//VelocityYint velocityX = (int) mVelocityTracker.getXVelocity();//VelocityX//不使用时回收内存mVelocityTracker.clear();mVelocityTracker.recycle();
自定义ViewGroup 获取子View
@Override protected void onFinishInflate() { super.onFinishInflate(); mTop = findViewById(R.id.id_stickynavlayout_topview); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mTopViewHeight = mTop.getMeasuredHeight(); }
这篇博客就先记录到这,下一篇在继续介绍!
- 自定义View技巧
- 自定义View——坑、技巧、调优
- objc.io 3.4 自定义View技巧 (转)
- Android 自定义View的一些使用技巧
- 自定义View或者ViewGroup的自定义属性使用技巧
- [自定义view]Android实战技巧之组合控件
- Android中自定义View中的使用与绘制技巧
- android技巧:在布局文件中加载外部自定义View
- Android 编程技巧之 ----- 自定义 View 踩坑总结
- 自定义view
- 自定义View
- 自定义view
- 自定义View
- 自定义View
- 自定义view
- 自定义View
- 自定义view
- 自定义view
- C++作业6
- C++作业6
- Druid架构说明
- git使用笔记
- secache 官方介绍
- 自定义View技巧
- Storm+Kafka应用场景
- C++实验6
- golang中的数组与切片
- HDU 1312
- secache 官方案例
- J
- 用js给div绑定事件,实现点击切换效果的几种方式总结
- Thread