最近在看博客的时候偶然看到一篇自定义View系列的教程,真心不错,特的转载过来以备不时之需!
路上的司机都知道自定义View有三个重要的步骤:measure,layout,draw。
关于这三个玩意,我们听得太多太多了,平常工作也基本上是这么按部就班的。
可是有一天,我们或许会发现许多关于自定义View的开发技术我们仅仅是掌握了常见的用法,但是没有去深入学习过;许多问题一知半解,知其然而不知其所以然。比如:
- 为什么父View影响到了子View的MeasureSpec的生成?
- 为什么自定义一个View在布局时将其大小设为wrap_content但其实际却是match_parent的效果?
- 子View的specMode和specSize生成的依据是什么?
- 为什么子View对于ACTION_DOWN返回false,系统就不会将ACTION_MOVE和ACTION_UP派发给该子View?
- 为什么子view对于Touch事件处理返回true那么其上层的ViewGroup就无法处理Touch事件?
- View的滑动冲突是怎么产生的,又该怎么处理?
- 为什么有时requestDisallowInterceptTouchEvent()会失效?
每当这个时候我都在想:要是有一个介绍自定义View的系列教程该有多好呀!要是有一个图能把繁杂的源码流程梳理得清清楚楚的那该有多好呀!
可是,在反复的搜索之后我的愿望依旧落空了,没有人给我准备好我需要的东西。当我想安慰自己再等等的时候,我猛地想起风韵犹存的郭大婶说的那句话:每当你在感叹,如果有这样一个东西就好了的时候,请注意,其实这是你的机会。
是啊,你怎么总是在期待别人把东西洗得干干净净,摆得整整齐齐地放在你面前呢?!?
对啊,自己动手实现这个期待难道不是一个最好的学习过程么?!
嗯哼,想到这里我决定了要自己梳理自定义View,而且要把它们弄成一个系列的。
首先在自定义View的时候,常常会用到一些Android系统提供的工具。这些工具封装了我们经常会用到的方法,比如拖拽View,计算滑动速度,View的滚动,手势处理等等。如果我们自己去实现这些方法会比较繁琐,而且容易出一些bug。所以,作为自定义View系列教程的开端,先介绍一下这些常用的工具,以便在后续的学习和工作中使用。
- Configuration
- ViewConfiguration
- GestureDetector
- VelocityTracker
- Scroller
- ViewDragHelper
嗯哼,它们都已经躺在这里了,我们就来挨个瞅瞅
Configuration
This class describes all device configuration information that can
impact the resources the application retrieves.
Configuration用来描述设备的配置信息。
比如用户的配置信息:locale和scaling等等
比如设备的相关信息:输入模式,屏幕大小, 屏幕方向等等
我们经常采用如下方式来获取需要的相关信息:
Configuration configuration=getResources().getConfiguration();
//获取国家码
int countryCode=configuration.mcc;
//获取网络码
int networkCode=configuration.mnc;
//判断横竖屏
if(configuration.orientation==Configuration.ORIENTATION_PORTRAIT){
} else {
}
ViewConfiguration
看完Configuration再来瞅ViewConfiguration。这两者的名字有些像,差了一个View;咋一看,还以为它俩是继承关系,其实不然。
官方对于ViewConfiguration的描述是:
Contains methods to standard constants used in the UI for timeouts,
sizes, and distances.
ViewConfiguration提供了一些自定义控件用到的标准常量,比如尺寸大小,滑动距离,敏感度等等。
可以利用ViewConfiguration的静态方法获取一个实例
ViewConfiguration viewConfiguration=ViewConfiguration.get(context);
在此介绍ViewConfiguration的几个对象方法。
ViewConfiguration viewConfiguration=ViewConfiguration.get(context);
//获取touchSlop。该值表示系统所能识别出的被认为是滑动的最小距离
int touchSlop = viewConfiguration.getScaledTouchSlop();
//获取Fling速度的最小值和最大值
int minimumVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
int maximumVelocity = viewConfiguration.getScaledMaximumFlingVelocity();
//判断是否有物理按键
boolean isHavePermanentMenuKey=viewConfiguration.hasPermanentMenuKey();
ViewConfiguration还提供了一些非常有用的静态方法,比如:
//双击间隔时间.在该时间内是双击,否则是单击
int doubleTapTimeout=ViewConfiguration.getDoubleTapTimeout();
//按住状态转变为长按状态需要的时间
int longPressTimeout=ViewConfiguration.getLongPressTimeout();
//重复按键的时间
int keyRepeatTimeout=ViewConfiguration.getKeyRepeatTimeout();
GestureDetector
大家都知道,我们可以在onTouchEvent()中自己处理手势。其实Android系统也给我们提供了一个手势处理的工具,这就是GestureDetector手势监听类。利用GestureDetector可以简化许多操作,轻松实现一些常用的功能。
嗯哼,来吧,一起瞅瞅它是怎么使用的。
第一步:实现OnGestureListener
private class GestureListenerImpl implements GestureDetector.OnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
System.out.println("---> 手势中的onDown方法");
return false;
}
@Override
public boolean onFling(MotionEvent e1,MotionEvent e2, float velocityX,float velocityY) {
System.out.println("---> 手势中的onFling方法");
return false;
}
@Override
public void onLongPress(MotionEvent e) {
System.out.println("---> 手势中的onLongPress方法");
}
@Override
public boolean onScroll(MotionEvent e1,MotionEvent e2, float distanceX,float distanceY) {
System.out.println("---> 手势中的onScroll方法");
return false;
}
@Override
public void onShowPress(MotionEvent e) {
System.out.println("---> 手势中的onShowPress方法");
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
System.out.println("---> 手势中的onSingleTapUp方法");
return false;
}
}
第二步:生成GestureDetector对象
GestureDetector gestureDetector = new GestureDetector(context,new
GestureListenerImpl());
这里的GestureListenerImpl就是GestureListener监听器的实现。
第三步:将Touch事件交给GestureDetector处理
比如将Activity的Touch事件交给GestureDetector处理
@Override
public boolean onTouchEvent(MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
比如将View的Touch事件交给GestureDetector处理
mButton=(Button) findViewById(R.id.button);
mButton.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View arg0, MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
});
VelocityTracker
这个玩意儿一看名字,大概就可以猜到意思了。嗯哼,速度追踪。
VelocityTracker用于跟踪触摸屏事件(比如,Flinging及其他Gestures手势事件等)的速率。
简单说一下它的常用套路。
第一步:开始速度追踪
private void startVelocityTracker(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
}
在这里我们初始化VelocityTracker,并且把要追踪的MotionEvent注册到VelocityTracker的监听中。
第二步:获取追踪到的速度
private int getScrollVelocity() {
mVelocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int) mVelocityTracker.getXVelocity();
return Math.abs(xVelocity);
}
同理可以获取1秒内Y方向所滑动像素值
第三步:解除速度追踪
private void stopVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
以上就是VelocityTracker的常用使用方式。
Scroller
Scroller挺常见的,用的比较多了。在此只强调几个重要的问题,别的就不再赘述了。
第一点:scrollTo()和scrollBy()的关系
先看scrollBy( )的源码
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
这就是说scrollBy( )调用了scrollTo( ),最终起作用的是scrollTo( )方法。
第二点:scroll的本质
scrollTo( )和scrollBy( )移动的只是View的内容,而且View的背景是不移动的。
第三点:scrollTo( )和scrollBy( )方法的坐标说明
比如我们对于一个TextView调用scrollTo(0,25) ;那么该TextView中的content(比如显示的文字:Hello)会怎么移动呢?
向下移动25个单位?不!恰好相反!!这是为什么呢?
因为调用该方法会导致视图重绘,即会调用
public void invalidate(int l, int t, int r, int b)
此处的l,t,r,b四个参数就表示View原来的坐标.
在该方法中最终会调用:
tmpr.set(l - scrollX, t - scrollY, r - scrollX, b - scrollY);
p.invalidateChild(this, tmpr);
其中tmpr是一个Rect,this是原来的View;通过这两行代码就把View在一个Rect中重绘。
请注意第一行代码:
原来的l和r均减去了scrollX
原来的t和b均减去了scrollY
就是说scrollX如果是正值,那么重绘后的View的宽度反而减少了;反之同理
就是说scrollY如果是正值,那么重绘后的View的高度反而减少了;反之同理
所以,TextView调用scrollTo(0,25)和我们的理解相反
scrollBy(int x,int y)方法与上类似,不再多说了.
ViewDragHelper
在项目中很多场景需要用户手指拖动其内部的某个View,此时就需要在onInterceptTouchEvent()和onTouchEvent()这两个方法中写不少逻辑了,比如处理:拖拽移动,越界,多手指的按下,加速度检测等等。
ViewDragHelper可以极大的帮我们简化类似的处理,它提供了一系列用于处理用户拖拽子View的辅助方法和与其相关的状态记录。比较常见的:QQ侧滑菜单,Navigation Drawer的边缘滑动,都可以由它实现。
ViewDragHelper的使用并不复杂,在此通过一个示例展示其常用的用法。
/**
* ViewDragHelper使用示例
* 原创作者:谷哥的小弟
* 原创地址:http://blog.csdn.net/lfdfhl
*/
public class MyLinearLayout extends LinearLayout {
private ViewDragHelper mViewDragHelper;
public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initViewDragHelper();
}
private void initViewDragHelper() {
mViewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
int fixedLeft;
View parent = (View) child.getParent();
int leftBound = parent.getPaddingLeft();
int rightBound = parent.getWidth() - child.getWidth() - parent.getPaddingRight();
if (left < leftBound) {
fixedLeft = leftBound;
} else if (left > rightBound) {
fixedLeft = rightBound;
} else {
fixedLeft = left;
}
return fixedLeft;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
int fixedTop;
View parent = (View) child.getParent();
int topBound = getPaddingTop();
int bottomBound = getHeight() - child.getHeight() - parent.getPaddingBottom();
if (top < topBound) {
fixedTop = topBound;
} else if (top > bottomBound) {
fixedTop = bottomBound;
} else {
fixedTop = top;
}
return fixedTop;
}
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
switch (state) {
case ViewDragHelper.STATE_DRAGGING:
System.out.println("STATE_DRAGGING");
break;
case ViewDragHelper.STATE_IDLE:
System.out.println("STATE_IDLE");
break;
case ViewDragHelper.STATE_SETTLING:
System.out.println("STATE_SETTLING");
break;
}
}
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
System.out.println("ViewCaptured");
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
System.out.println("ViewReleased");
}
});
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
mViewDragHelper.processTouchEvent(ev);
return true;
}
}
从这个例子可以看出来ViewDragHelper是作用在ViewGroup上的(比如LinearLayout)而不是直接作用到某个被拖拽的子View。其实这也不难理解,因为子View在布局中的位置是其所在的ViewGroup决定的。
在该例中ViewDragHelper做了如下主要操作:
(1) ViewDragHelper接管了ViewGroup的事件拦截,请参见代码第91-94行
(2) ViewDragHelper接管了ViewGroup的Touch事件,请参见代码第98-102行
(3) ViewDragHelper处理了拖拽子View时的边界越界,请参见代码第22-55行
(4) ViewDragHelper监听拖拽子View时的状态变化,请参见代码第58-72行
除了这些常见的操作,ViewDragHelper还可以实现:抽屉拉伸,拖拽结束松手后子View自动返回到原位等复杂操作。
好了,了解完这些非常有用的工具,我们就正式进入自定义View。
原文链接:http://blog.csdn.net/lfdfhl/article/details/51324275