3.1 View有关的基础知识
3.1.1 什么是View
view是Android中所有控件的基类。ViewGroup也继承了View,意味着view本身就可以是单个控件也可以是多个控件组合成的多个控件。
-
View的位置参数: View的位置由对应的top,left,right,bottom这四个顶点决定的。
1)View的宽高和坐标关系:width = right - left,height = top - bottom。
2)View在平移过程中,top和left表示的是原始左上角的位置信息,其值不会改变,发生改变的是x、y、translationX、translationY这四个参数,x是View左上角的坐标,translation是view移动后相对于父容器(这里其实就是刚才说的左上角)的 偏移量,所以有x = left + translationX。y的原理相同,即y=top+translationY。 -
MotionEvent典型事件:ACTION_DOWN, ACTION_MOVE, ACTION_UP。
-
TouchSlop:系统所能识别的被认为是滑动的最小距离,我们可以用这个常量来判断用户的滑动是否达到阈值,提升用户体验,书上获取的大小是8dp(其他的应该也差不了太多)。 获取方法:ViewConfiguration.get(getContext()).getScaledTouchSlop()。
-
VelocityTracker加速度追踪:使用很简单,看书就好。经过测试一般建议类似ViewPager这样的空间,将时间间隔设置为1000(也就是1秒)时,加速度阈值设为1000-2000左右体验较好,各位可自行测试。
- GenstureDetrctor的使用:详细介绍下,也给之前做个总结:
- 首先明确书中提出的建议:在实际的开发中,可以不使用GenstureDetrctor,完全可以在自己的onTouchEvent( )方法中实现所需的监听。可以按照以下原则进行取舍:
- 如果只是监听滑动相关的,直接在OnTouchEvent( )实现即可;
- 监听双击行为的--->那么就使用GenstureDetrctor。
- 首先建立全局变量GenstureDetrctor,在OnCreate( )方法中创建一个GenstureDetrctor对象,并且实现OnGenstureListener接口,实现双击的话还要实现OnDoubleTapListener接口:
- 接着在接管的View中的OnTouchEvent()中,添加
fab.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
boolean consume = mGestureDetector.onTouchEvent(event);
return consume;
}
});
- 首先明确书中提出的建议:在实际的开发中,可以不使用GenstureDetrctor,完全可以在自己的onTouchEvent( )方法中实现所需的监听。可以按照以下原则进行取舍:
3.2 View的滑动
概述:View的滑动是一个很重要的知识点,务必掌握。不管一些滑动的效果是多么的绚丽,归根结底都是由不同的滑动外加一些特效所组成的。所以说掌握滑动的方法是实现绚丽自定义控件的基础。
-
View的滑动:
1)ScrollTo和ScrollBy,简单归纳下:-
View提供了专门的ScrollTo和ScrollBy()方法来实现这个功能,ScrollBy实际上也是调用了ScrollTo,这里的重点是要理解View内部的两个属性mScrollX和mScrollY的改变规则,这两个属性可以通过getScrollX和getScrollY得到,概述以下:这里的mScrollX总是等于View的左边缘和View内容左边缘在水平方向的距离,同理mScrollY是View的上边缘和View内容上边缘的距离,View边缘指的是View的位置由四个顶点组成。而View内容边缘指的是View中内容的边缘。
-
ScrollTo和ScrollBy只能改变view的内容边缘而不能改变View在布局中的位置。mScrollX和mScrollY的单位是像素,View的左边缘在View内容的右侧的时候mScrollX位正值,View的上边缘在View内容的下侧的时候mScrollY为正值。总结一句话就是ScrollTo和ScrollBy只能改变view的内容边缘而不能改变View在布局中的位置,只能将View的内容进行移动,而View本身在布局中的位置是不变的。
ScrollBy的0点在一般情况下均为可见的top那条线,有一种特殊情况就是(书中未提及)当某个ViewGroup在内部的layout的时候设置margin为负值的View时,0点会在可见top上方的高度(或宽度)为margin的地方,这个鬼东西实在是太绕口,参考上滑下滑刷新,参考headerView的设置。竖向滑动时,上滑ScrollY不断增加(所以应该传正值),下滑时ScrollY不断减少(所以应该传负值);同理,横向滑动时,左滑ScrollX不断增加,右滑不断减少。
ScrollTo可以理解为把View滑动到ScrollX或ScrollY为指定值的位置(看源码也是这意思~) 。我的个人理解是:以整个定义的坐标方法理解View的内容不懂,View本身沿着X的正方向就是mScrollX为正值,同理View本身沿着Y的正方向,mScrollY是正值。
2)动画:注意View动画的View移动只是位置移动,其本身还是在原来位置,会导致一些bug。 --》后期自己要专门针对动画,好好写一个。
3)通过LayoutParams:这里有两个技巧:- 得到LayoutParams,直接对View进行操作:
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) fab.getLayoutParams();
layoutParams.width+=100;
layoutParams.leftMargin+=100;
fab.requestLayout(); - 间接操作:按照布局(假如父布局是LinearLayout),在左边放歌宽度为o的,需要时候再设置宽度即可。
- 得到LayoutParams,直接对View进行操作:
- 三者之间的对比:直接写个可以随着手指滑动的例子就是书上的代码:
package com.fightzhao.gesturedetectordemo.ui; import android.annotation.SuppressLint; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.widget.TextView; import com.nineoldandroids.view.ViewHelper; /** * Created by fightzhao on 16-3-6. */ public class TestButton extends TextView { private static final String TAG = "TestButton"; private int mScaledTouchSlop; // 分别记录上次滑动的坐标 private int mLastX = 0; private int mLastY = 0; public TestButton(Context context) { this(context, null); } public TestButton(Context context, AttributeSet attrs) { super(context, attrs); init(); } public TestButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { mScaledTouchSlop = ViewConfiguration.get(getContext()) .getScaledTouchSlop(); Log.d(TAG, "sts:" + mScaledTouchSlop); } @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getRawX(); int y = (int) event.getRawY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; Log.d(TAG, "move, deltaX:" + deltaX + " deltaY:" + deltaY); int translationX = (int)ViewHelper.getTranslationX(this) + deltaX; int translationY = (int)ViewHelper.getTranslationY(this) + deltaY; ViewHelper.setTranslationX(this, translationX); ViewHelper.setTranslationY(this, translationY); break; } case MotionEvent.ACTION_UP: { break; } default: break; } mLastX = x; mLastY = y; return true; } }
三者之间的比较:
- scrollTo/scrolBy:View提供的原生方式,其作用是专门用于滑动,可以比较方便的实现滑动的效果而且不影响内部的点击事件。缺点:很显然只能滑动view的内容,并不能滑动view本身
- 动画:Android3.0以上没有上面明显缺点。主要适用于没有交互的View和实现复杂的动画效果。
- 改变布局方式:操作稍微复杂,主要用于有交互的View。
-
3.3 弹性滑动
弹性滑动就是比较生硬的滑动过去。
3.3.1使用Scroller
这是Scroller的典型用法:
-
Scoller:本人不才,这个鬼东西我也是看了两三天,自己写了好多测试代码才真正理解,虽然代码很好写。具体的使用方法大家看书就好,说得很清楚,我就讲几种使用弹性滑动的场景吧,这个场景有助于大家去理解scroller代码的使用,这里以原书附带的源码HorizontalScrollViewEx为例:
1)TouchEvent的ACTION_UP事件中,用户滑动速度很快,但是滑动距离又不足以“翻页”的时候,通过scroller来帮助用户scrollBy掉滑动一页还需要的dx或dy。
2)当用户滑动到最上端或最下端时,我们仍然允许用户继续滑动,但是一旦松手,就把页面弹回到最上端和最下端的位置,用IOS的用户都知道IOS几乎所有页面都有这个弹性效果,用户体验非常好,其实我们用scroller也能轻松实现。
3)第三种场景和第一种类似,大家依然可以参考我上面发的那篇仿网易的blog,现在不是翻页,当用户滑动的加速度很大的时候,我们认为用户需要滑动的距离肯定是不只他手从放下到松开的那段距离的,所以这种情况下我们需要通过scroller帮助用户去多滑一段,这个距离具体设置一般需要交互给出,我测试的时候设置的是滑加速度的十分之一,感觉还是太少,各位可以自行设置。 -
View事件的分发机制:不得不说,主席这段写得实在精彩,我看了这么多博客,书籍,真心是写得最棒的,说得最清楚的,同时如果你看不懂,直接按照他那个代码复制粘贴,基本上小的问题都能解决了。
1)三大方法关系的伪代码(屌炸天系列~)
public boolean dispatchTouchEvent(MotionEvent ev)
{
boolean consume = false;if(onInterceptTouchEvent(ev))
{
consume = onTouchEvent(ev);
}
else
{
consume = child.dispatchTouchEvent(ev);
}return consume;
}
关系很清楚了,如果当前View拦截事件,就交给自己的onTouchEvent去处理,否则就丢给子View继续走相同的流程。
2)onTouchListener优先级高于onTouchEvent。
3)事件传递顺序:Activity -> Window -> View,如果View都不处理,最终将由Activity的onTouchEvent处理。
4)一些结论:拦截的一定是事件序列;不消耗ACTION_DOWN,则事件序列都会由其父元素处理;只消耗ACTION_DOWN事件,该事件会消失,消失的事件最终会交给Activity来处理;requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,除了ACTION_DOWN; -
事件分发源码解析:这里具体的分析就请看书了,列一些有价值的tips
1)Window的实现类为PhoneWindow。
2)获取Activity的contentView的方法((ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0); -
View的滑动冲突处理,这里没什么好tips的了,因为主席已经提供了非常棒的外部拦截法和内部拦截法,普通需求基本直接复用代码就能搞定。但是如果想要深刻理解,只有自己多写多测多读代码,才能很好的掌握,对于自定义View来说,掌握这个专题将对你的功力有大幅的提升。