View的总结

从开始学安卓就一直在各种view,总感觉自己应该写下来比较重要的一些View的知识点(对于我来说比较重要的点)。

view是什么呢?

其实像button,TextView这些控件的基类都是view。而LinearLayout,RelativeLayout这些既是view,也是Viewgroup。

很多时候view和ViewGroup其实不分家。那么他们的区别是什么呢?

ViewGroup是包含多个控件,一组view。而viewGroup也可以作为一个view而存在使用,怎么理解这句话?

现在有一个LinearLayout,它里面又有一个Button,一个TextView。此时的LinearLayout则是ViewGroup,而button和textView则是view。在LinearLayout外面其实还有一个Relativelayout包裹着它,那么此时的LinearLayout又是一个View存在于RelativeLayout,此时的RelativeLayout便是一个Viewgroup。(以后看到这个解释,自己也应该懂这两者的区别了吧。)

在Android3.0后view多了几个参数(x,y,translationX,translationY);

x,y:是view左上角坐标(即上图的left和top)。x=left+translationX;     y=top+translationY;

translationX,translationY:是左上角相对于父容器的偏移量。如何理解此处的偏移量?

其实偏移量就是指平移的距离。比如向右下角移动100,此时的translationx=100,translationY=-100;(有点疑问是translationY=-100还是100)

如何确定view(button,TextView...)的位置呢?

view的位置主要是由4个顶点来确定的,分别就是top,left,right,bottom.这四个点确定了,view的位置也就确定了。

如图所示,可以看到top,left,right,bottom,以及view和ViewGroup的关系。view位置坐标和父容器的关系图。

在View里我们也有方法可以直接获取view的left,right,top,bottom的值。

left=getLeft();right=getRight();top=getTop();bottom=getBottom();

获取view的宽高?

根据上面的写法可以写成:(后面红色部分为,在项目中的写法)

width=right-left;--->width=getRight()-getLeft();

height=bottom-top;--->height=getBottom()-getTop();

很多时候我们需要用手势来操作我们的view。紧接着就赶紧来说说那些年的那些手势,以及手势回调的东西。

最出名的就是MotionEvent啦!耳熟能详的东西还是要写一写,以免自己忘了。

ACTION_DOWN:按屏幕的触发

ACTION_MOVE:移动的触发

ACTION_UP:手指离开屏幕的触发

肯定也是有获取手点击时候屏幕的坐标方法。方法有两种,肯定是有区别的,这也是需要记住的一个点。

getX/getY:相对于当前view左上角的x,y坐标

getRawX/getRawY:相对于手机屏幕左上角的x,y坐标

很多时候我们可能在滑动某个东西的时候,也希望给它一点滑动条件,而我们每个手机其实都有一个默认的最小滑动距离。它的名字就叫做----TouchSlop,这个玩意儿是系统所能识别的能被拦截的最小的滑动距离。这是个常量值,它和自己的手机有关,不同设备,可能值是不同滴。不能理解?再来细细解释一下,滑动的距离<这个常量。不用程序猿自己去判断,系统都会帮你判断成你没有进行滑动操作生气最后就是获取这个常量的方法:myTouchSlop=ViewConfiguration.get(getContext()).getScaledTouchSlop();  哎!就这么一句话,好轻松啊。

有的时候某些的滑动得速度真的是快得能猜测你单身多少年了。没事,你速度再快,view里面也有方法来进行追踪。

速度追踪VelocityTracker追踪手机滑动过程中的速度,包括了水平,竖直方向。

先在onTouch中追踪当前单击事件的速度VelocityTracker velocityTracller=VelocityTracker.obtain();

velocityTracker.addMovement(event);

获取当前速度:velocityTracker.computeCurrentVelocity(1000);  记住这个1000,它身上有戏

int xVelocity=(int)velocityTracker.getXVelocityTracker.getXVelocity();

int yVelocity=(int)velocityTracker.getYVelocityTracker.getYVelocity();

这里用法就是这样,但是切记,在调用getXVelocity()之前,一定要先调用computeCurrentVelocity(某个时间)这个方法。再来说一下这个1000的戏,这个1000代表的是一个时间,比如你要得到1000ms内的速度,比如你1000ms移动了20像素,那么也就是20像素每秒。这里的1000ms时间间隔我们可以根据自己的需求来任意代替。

如果我们不用velocityTracker了,要记得回收!velocityTracker.clear();velocityTracker.recycle();千篇一律的回收方式。

下面就来说一下手势检测这个东西生气GestureDetector。 你可能第一反应手势检测就是MotionEvent的touch,nonono~还是太年轻了,touch一般用来检测单击事件。那如果现在需要你检测双击,长按,滑动的东西呢?GestureDetector为你排忧解难。特别是双击这种事,最好是都用GestureDetector来处理。不扯那么多,直接来干货。

GestureDetector.OnDoubleTapListener gestureDetector= new GestureDetector.OnDoubleTapListener() {
    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        //它用于判断用户是不是双击,如果点了第一下,很久没点第二下,判断为单击,否则为双击
        return false;
    }

    @Override
    public boolean onDoubleTap(MotionEvent e) {
        //在双击的第二下,Touch=ACTION_DOWN时触发
        return false;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        //双击之间发生的事件。也就是doubleTap的时候的down,up事件,这两个事件由此函数来通知
        return false;
    }
};

注释在一般看的时候不用管,具体用的时候搞清楚他们的方法是什么意思就行了。

一般在开发中不用GestureDetector,双击情况下考虑它就行了。

Scroller弹性滑动:弹性滑动也就是规定某个时间,让滑动事件在这个时间内缓慢滑动完成。实现过渡效果的滑动。

使用代码如下所示:其实使用也就不这样使用,只是要配合computeScroll一起使用而已。注意,startScroll里面的1000是指这个滑动要执行的时间。

//Scroller滑动的使用
Scroller scroller=new Scroller(mContext);
private void smothScrollerTo(int destX,int destY){
    int scrollX=getScrollX();
    int delta=destX-scrollX;
    scroller.startScroll(scrollX,0,delta,0,1000);
    invalidate();
}
//scroller的使用一定要配合compuuteScroll来使用
@Override
public void computeScroll() {
    super.computeScroll();
    if(scroller.computeScrollOffset()){
        scrollTo(scroller.getCurrX(),scroller.getCurrY());
        postInvalidate();
    }
}

view里面还有一种滑动,scrollTo和scrollBy。这两个方法,只能改变view内容的位置,而不可改变view在布局中的位置

看了源码之后其实scrollBy内部的实现还是scrollTo。那你肯定想问,那为毛会有两个这个,这不是脱了裤子放屁吗?先不急,既然有两个方法,必定有不同和各自存在的必要性。

scrollTo(int x,int y):基于相对于view的初始位置。简单点,如果view的初始位置是在(-20,-20),scrollTo它移动的初始位置都是基于(-20,-20)来移动的,如果此时调用scrollBy(-40,-40),此时view内容的位置则为(-60,-60),此时再来调用scrollTo(-100,-100),view内容的位置则为(-120,-120),而不是(-160,-160)。由此可见两者之前起始位置是完全不同的。

scrollBy(int x,int y):基于所相对于当前位置它是相对我们当前位置的偏移。白话解释就是,假设view初始位置在(-20,-20),内容被移动到(-100,-100)了。此时调用scrollBy(-20,-20)则将其位置变为(-120,-120)了。

这里的x,y是指偏移量。偏移量还有一点需要知道,x=100,则是把view的内容从view初始位置向左移动100.x=-100,把view的内容从view初始位置向右移动100。y=100,则是把view的内容从view初始位置向上移动100.y=-100则是向下移动100。

实现view的滑动除了上面的几种scrollxx的方式,别忘了,我们还有调皮活跃的动画,用动画来移动view

,我先用帧动画的形式来完成view的滑动/平移(也就是view动画)。这里感觉挺简单的,<set>标签,然后设置fromXdelta和toXDelta这些东西,就可以实现了。是不是很easy?接着要说一下这里有个需要注意的点,fillAfter这个东西,眼睛正常的话应该都看到了,它设置true和false对我们的view的这段平移有什么影响呢?我首先要说一句,这影响大了去了。

设置fillAfter=true,则表示view移动到右下角的位置之后,就会留在那个右下角的位置。要是fillAfter=false,则表示view移动到右下角后,完成平移之后,就又会返回到原来的位置去,也就是一个瞬时操作,这么个样子。这么一说,是不是一下就明白了?再提一句,trasnlate标签就是平移的意思。说完了这个,我觉得有必要再说一下属性动画。来,眼看下来~

第二.先不说属性动画怎么用啊之类的,我们就简单的做移动操作好了,他也可以控制view的滑动平移。就这么一句话,很easy啊。

但是啊,这儿还有一个点需要记一下,上面用view动画,也就是<set>这种,其实并没有改变view控件真正的位置,它只是改变我们视图所见的位置,如果view是个button,button的点击事件在新位置,并不会生效,而点击原来的初始位置button还有响应。是不是很坑爹?但是呢,属性动画可以解决这个问题哦。属性动画就是直接改变了view控件的位置。但是呢Android3.0及以上才能用属性动画。哎,好气啊~我们如果真的要解view动画的这种问题,就只有在移动后,在新位置新建一个view的button控件,再给新的button控件添加一个点击事件。这种好鸡肋呀~

//从原始位置向右平移100像素,用时1000ms
ObjectAnimator.ofFloat(myview,"translationX",0,100).setDuration(1000).start();

得意还有一种移动View的方法,那就是设置LayoutParams。在这里我给button设置了一个marginleft=100。设置的话有两种方式,自己选择。

ViewGroup.MarginLayoutParams params=(ViewGroup.MarginLayoutParams) button.getLayoutParams();
params.width=+10;
params.leftMargin+=100;
//两种写法都可以
//button.requestLayout();
button.setLayoutParams(params);

上面差不多说了3种滑动改变view的方法,各自的优缺点还是应该总结一下的。不哔哔,看图

scrollTo/By:操作是简单,但是它移动的是view的内容,而非view本身。和view动画不同,它不影响控件的点击那些。

动画:属性动画其实没有什么毛病。但是如果是View动画会有一些毛病。

改变布局参数:其实它也没有多少毛病。

像位移啥的view的setTranslationX/Y之类的方法,view的这些set方法,但是这些方法是3.0及以上的才能用。为了兼容,可以用viewHelper的这些各种set方法。意思是一样的。比如还有setX,setScaleX,setAlpha....这些,都可以用viewHelper来弄。

弹性滑动的实现方式生气其实也就是实现缓冲滑动~

A.弹性滑动Scroller。也就是给滑动一个缓冲过程,说白了也就是给滑动一个延迟时间。Scroller没有调用view对象就实现了这个过程。其内部是如何实现的呢?

scroller本身是无法移动view的,他是根据view的computeScroll方法来不断让view重绘,每一次重绘和上一次的绘制都有一段时间,拿到这段时间,我们的scroller可以得到当前的位置。知道了那段时间后的滑动位置,我们可以用scrollTo来实现两个位置间的滑动,所以说每要重绘一次都会scrollTo一次,就给我们造成了一种视觉“缓冲”的滑动效果。

B.动画。动画的原理其实和Scroller差不多,也是通过scrollTo来实现,然后在某个时间段内完成这个动画(也就是设置的那一个缓冲的时间)。我们可以拿到动画帧数已经滑动的部分的比例,根据这个比例可以得到我们要scrollTo的位置。然后我们就scrollTo,然后完成弹性滑动。

C.延时策略。用handler或view的postDelayed。还可以使用线程的sleep来弄。。。具体去看139页的demo。

View的事件分发(这个真的很重要啊!用的多,问得也多!)

很多时候开发出现的什么滑动冲突都是因为没有解决好事件分发这个事儿。来吧。。。慢慢看吧。总要学会的嘛。

A.点击事件的事件传递

dispatchTouchEvent(MotionEvent ev);

事件分发。研究了一下里面的源码,其实里面包含了事件拦截,而onIntercept的调用就是在dispatch中发起的。于我们程序员而言影响不大,但是它里面是做了一层性能优化的,如果dispatch拦截了,那么就不会再去判断一次onItercept了。也不会再调用onIntercept方法,提升性能。

onInterceptTouchEvent(MotionEvent ev);

判断是否拦截某个事件。返回结果表示拦截当前事件。注意:activity和view都没有拦截方法。若Activity拦截,则屏幕没有响应。view是事件传递的最底层,要么消费掉,要么就回传。

onTouchEvent(MotionWvent ev);

用来处理点击事件。返回结果表示是否处理当前事件。

View的滑动冲突(都是固定套路)

view的滑动冲突大致有几种情况,且解决办法也是固定的套路。先来分析有几种情况

1.外部滑动方向和内部滑动方向不一致。

2.外部滑动方向和内部华东方向一致。

3.前面2种情况的结合。

对于1里面的情况,我们可能遇见的情况是:ViewPager和Fragment的嵌套使用。左右滑动来切换页面,如果ViewPager里面是嵌套的ScrollView。那么我们可以根据滑动的水平和竖直方向的距离来进行判断是哪个方向的操作。

处理滑动冲突的方法:外部拦截法和内部拦截法

①外部拦截法:点击事件经过父容器的处理,父容器需要该事件就拦截,不需要该事件就不拦截。需要重写父容器的onInterceptTouchEvent.父容器有点特殊,一旦它开始拦截某个事件,那么后面的事件都会交给他来处理(其实这句话有点没太懂,哈哈哈)。

示例如下

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted=false;
        int x= (int) ev.getX();
        int y= (int) ev.getY();
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                intercepted=false;
                break;
            case MotionEvent.ACTION_MOVE:
                if(a>b){//父容器要拦截当前事件
                    intercepted=true;
                }else{//父容器不拦截
                    intercepted=false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted=false;
                break;
        }
        return intercepted;
    }

②内部拦截法:指父容器不做任何拦截,全交给子元素,子元素需要就直接消费掉事件,子元素不需要就交给父容器处理。需要requestDisallowInterceptTouchEvent方法来配合才能正常使用。我们需要重写子线程的dispatchTouchEvent方法。

示例如下:

private LinearLayout parent;
    private int mLastX;
    private int mLastY;
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int x= (int) ev.getX();
        int y= (int) ev.getY();
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                parent.requestDisallowInterceptTouchEvent(false);
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX=x-mLastX;
                int deltaY=y-mLastY;
                if(deltaY>deltaX){//父容器要处理这个事件
                    parent.requestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        mLastX=x;
        mLastY=y;
        return super.dispatchTouchEvent(ev);
    }

子元素调用parent.requestDisallowInterceptTouchEvent();父元素才能继续拦截所需要的事件。

当面对不同的滑动策略时,只需要修改里面的条件就可以了,除了子元素要做处理之外,父元素也要拦截ACTION_DOWN以外其他事件。当子元素调用parent.requestDisallowInterceptTouchEvent(false);的时候父元素才能继续拦截所需的事件。

但是内部拦截法的时候,父元素不可对ACTION_DOWN进行拦截。因为父容器一旦拦截ACTION_DOWN,子元素就不能接收到事件,内部拦截法也就不能起作用了。

父元素的拦截处这样处理:

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if(ev.getAction()==MotionEvent.ACTION_DOWN){
            return false;
        }else{
            return true;
        }

    }

案例:假设外层父元素是一个HorizonScrollView,子元素是一个listView。父控件是左右划动,子元素是上下滑动。

此时我们用外部拦截法来做:HorizonScrollView的代码就应该是如下:

private Scroller mScroller = new Scroller(context);
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted=false;
        int x= (int) ev.getX();
        int y= (int) ev.getY();
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                intercepted=false;
                if(!mScroller.isFinished()){//判断滑动是否结束
                    mScroller.abortAnimation();//优化滑动体验
                    intercepted=true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int mX=x-mLastX;
                int mY=y-mLastY;
                if(Math.abs(mX)>Math.abs(mY)){//父容器要拦截当前事件
                    intercepted=true;
                }else{//父容器不拦截
                    intercepted=false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted=false;
                break;
        }
        mLastXInteger=x;
        mLastYInteger=y;
        return intercepted;
    }

分析完了外部拦截法,我们来用内部拦截法来处理一下:

private onInterceptTestView onInterceptTestView;
    private int mLastX;
    private int mLastY;
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int x= (int) ev.getX();
        int y= (int) ev.getY();
        switch(ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                onInterceptTestView.requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                int delX=x-mLastX;
                int delY=y-mLastY;
                if(Math.abs(delX)>Math.abs(delY)){
                    onInterceptTestView.requestDisallowInterceptTouchEvent(true);
                }else{
                    onInterceptTestView.requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        mLastX=x;
        mLastY=y;
        return super.dispatchTouchEvent(ev);
    }

除此之外,HorizonScrollView里面也要做出相应的改变:

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int x= (int) ev.getX();
        int y= (int) ev.getY();
        if(ev.getAction()==MotionEvent.ACTION_DOWN){
            mLastX=x;
            mLastY=y;
            if(!mScroller.isFinished()){
                mScroller.abortAnimation();
                return false;
            }
        }else{
            return true;
        }
        return super.onInterceptTouchEvent(ev);
    }

以上我们分析的便是情况1里面的滑动冲突解决方式。

注意:一般还是尽量使用外部拦截法。因为外部拦截法比较符合事件传递机制,且逻辑那些都要简单一些。内部拦截法相对要复杂一些。

关于View的解决套路,差不多就这样了。后续继续补充。关于View的原理,就写在另外一篇博客了。

猜你喜欢

转载自blog.csdn.net/sweet_smile5/article/details/79883090