安卓自定义控件常用对象及方法

来源:互联网 发布:北伐战争知乎 编辑:程序博客网 时间:2024/05/17 01:11

自定义控件常用对象及方法

常用对象:

VelocityTracker:

android中主要应用于touch event VelocityTracker通过跟踪一连串事件实时计算出当前的速度。

android.view.VelocityTracker主要用跟踪触摸屏事件(flinging事件和其他gestures手势事件)的速率。用addMovement(MotionEvent)函数将Motion event加入到VelocityTracker类实例中.你可以使用getXVelocity()getXVelocity()获得横向和竖向的速率到速率时,但是使用它们之前请先调用computeCurrentVelocity(int)来初始化速率的单位

用法:

@Override
public boolean onTouchEvent(MotionEventevent) {
   
if (mVelocityTracker==null) {
       
mVelocityTracker =VelocityTracker.obtain();
   
}
   
mVelocityTracker.addMovement(event);

case MotionEvent.ACTION_UP:
      

            mVelocityTracker.computeCurrentVelocity(1000);
            float yVelocity =mVelocityTracker.getYVelocity();
           
if (yVelocity>standerSpeed|| ((getScrollY() +mHeight/ 2) /mHeight< mStartScreen)) {
               
mState =State.ToPre;
            }else if (yVelocity< -standerSpeed|| ((getScrollY() + mHeight /2) /mHeight > mStartScreen)) {
            
changeByState(yVelocity);
        }
       
if (mVelocityTracker!=null) {
           
mVelocityTracker.recycle();
            mVelocityTracker=null;
        }
       
break;

}

 

 

 

Region

Region rgn = new Region();  

 rgn.setPath(ovalPath,new  Region(5050200200));  

region这个对象很好用,我们可以用他来判断并获取几个region的相交的部分,同时还可以判断点击的x,y是不是在region区域内。对于不规则控件的点击就很好用。

http://www.gcssloop.com/customview/touch-matrix-region

这个demo讲的很不错哦。

我们可以再一个大区域里面获取小区域:

 

right_p =new Path();

right =new Region();

 

@Override

    protectedvoidonSizeChanged(int w, int h,int oldw,int oldh){

        super.onSizeChanged(w, h, oldw, oldh);

   

        //注意这个区域的大小

        Region globalRegion =new Region(-w, -h, w, h);

right_p.addArc(bigCircle, -40, bigSweepAngle);

        right_p.arcTo(smallCircle,40, smallSweepAngle);

        right_p.close();

        right.setPath(right_p, globalRegion);

}

 

在获取区域后我们可以在ontouch中判断点击的x,y是不是在region区域内:

int x = (int) event.getX();

int y = (int) event.getY();

right.contains(x,y)

注意:

开启硬件加速情况下 event.getX() 和不开启情况下 event.getRawX() 等价,获取到的是屏幕(物理)坐标 (本文的锅)。

开启硬件加速情况下 event.getRawX() 数值是一个错误数值,因为本身就是全局的坐标

又叠加了一次 View 的偏移量,所以肯定是不正确的 (本文的锅)。

 

两个region可以做如下的操作:


//构造两个Region  

        Region region = new Region(rect1);  

        Region region2= new Region(rect2);  

          

        //取两个区域的交集        

        region.op(region2, Op.INTERSECT);  

 

 

 

Scroller

Scroller是计算结果来影响scrollTo,scrollBy,从而使得滑动发生改变。同时还可以判断当前滑动的状态。

用法:

1. 创建Scroller的实例 
2. 调用startScroll()方法来初始化滚动数据并刷新界面 
3. 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑

这里scroller仅仅是通过startScroll()设置一个滑动的开始点和结束点的一个目标。然后每次在draw之后去重新确定我们下一个目标点在哪里,然后真正的执行者是scrollTo,scrollBy 

  

public ScrollerLayout(Context context, AttributeSet attrs) {

       super(context, attrs);

       // 第一步,创建Scroller的实例

       mScroller = new Scroller(context);

}

 

@Override

    publicboolean onTouchEvent(MotionEvent event) {

          ………

            caseMotionEvent.ACTION_UP:

                //当手指抬起时,根据当前的滚动值来判定应该滚动到哪个子控件的界面

                inttargetIndex = (getScrollX() + getWidth() /2) /getWidth();

                int dx =targetIndex * getWidth() - getScrollX();

                //第二步,调用startScroll()方法来初始化滚动数据并刷新界面

                mScroller.startScroll(getScrollX(),0, dx,0);

                invalidate();

                break;

        }

        returnsuper.onTouchEvent(event);

    }

 

@Override

    publicvoid computeScroll() {

        // 第三步,重写computeScroll()方法,并在其内部完成平滑滚动的逻辑

        if (mScroller.computeScrollOffset()){

            scrollTo(mScroller.getCurrX(),mScroller.getCurrY());

            invalidate();

        }

    }

加入scroller是的控件的滑动更加平滑!

这里computeScrollOffset()方法判断滑动是否结束,他的内部代码通过:1、是否在滑动的标志flag;2、是否到达指定的duration;3、目标坐标是否和当前坐标一致;来判断滑动是否结束。

在一个对象中用到scroll,是对这个对象进行滑动!如果想要单独对对象里面的子控件进行拖动,可以考虑下dragViewHelper。

 

ViewConfiguration

包含了方法和标准的常量用来设置UI的超时、大小和距离 。

 

Camera

在graphics包下,Camera用来计算3D转换、生成矩阵,然后应用在画布上。

Camera的坐标系是左手坐标系。


public Rotate3DAnimation(Contextcontext,float fromDegrees,float toDegrees,

                             float centerX,floatcenterY,float depthZ,boolean reverse) {

        mFromDegrees = fromDegrees;

        mToDegrees = toDegrees;

        mCenterX = centerX;

        mCenterY = centerY;

        mDepthZ = depthZ;

        mReverse = reverse;

        // 像素密度

        scale =context.getResources().getDisplayMetrics().density;

    }

 

    @Override

    public voidinitialize(int width,intheight,int parentWidth,int parentHeight) {

        super.initialize(width,height, parentWidth, parentHeight);

        mCamera = newCamera();

    }

 

    @Override

    protectedvoidapplyTransformation(float interpolatedTime, Transformation t) {

        finalfloat fromDegrees = mFromDegrees;

        floatdegrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);

        finalfloat centerX = mCenterX;

        finalfloat centerY = mCenterY;

        finalCamera camera = mCamera;

        finalMatrix matrix = t.getMatrix();

        camera.save();

 

        // 调节深度

        if(mReverse) {

            camera.translate(0.0f, 0.0f,mDepthZ * interpolatedTime);

        } else{

            camera.translate(0.0f, 0.0f,mDepthZ * (1.0f - interpolatedTime));

        }

 

        // y轴旋转

        camera.rotateY(degrees);

 

        camera.getMatrix(matrix);

        camera.restore();

 

        // 修正失真,主要修改 MPERSP_0 MPERSP_1

        float[] mValues = newfloat[9];

       matrix.getValues(mValues);               //获取数值

        mValues[6] = mValues[6]/scale;            //数值修正

        mValues[7] =mValues[7]/scale;            //数值修正

       matrix.setValues(mValues);               //重新赋值

        // 调节中心点

        matrix.preTranslate(-centerX,-centerY);

        matrix.postTranslate(centerX, centerY);

    }

}

最经典的例子只能是官方示例-Rotate3dAnimation。

 

Matrix

参考:http://www.cnblogs.com/zhouyang209117/p/5100977.html

set、pre 与 post

对于四种基本变换平移(translate)、缩放(scale)、旋转(rotate)、错切(skew) 它们每一种都三种操作方法,分别为设置(set)、前乘(pre) 和后乘 (post)。而它们的基础是Concat,通过先构造出特殊矩阵然后用原始矩阵Concat特殊矩阵,达到变换的结果。

 

在Camera类的例子里有一个:

// 调节中心点

matrix.preTranslate(-centerX,-centerY);

matrix.postTranslate(centerX,centerY);

preTranslate是指在setScale前,平移,postTranslate是指在setScale后平移注意他们参数是平移的距离,而不是平移目的地的坐标!

由于缩放是以(0,0)为中心的,所以为了把界面的中心与(0,0)对齐,就要preTranslate(-centerX, -centerY),

setScale完成后,调用postTranslate(centerX,centerY),再把图片移回来,这样看到的动画效果就是activity的界面图片从中心不停的缩放了。

canvas.concat的作用可以理解成对matrix的变换应用到canvas上的所有对象(是用matrix画上去的多有对象).

可以参考下面:

 

 

GestureDetector

(这他喵的是2.2的方法,我到6.0才发现,相见恨晚啊)

Android提供了一个手势监听类GestureDetecto,对触屏事件的处理。

GestureDetector  mGestureDetector = newGestureDetector(this,newMyOnGestureListener());

 

mButton.setOnTouchListener(new OnTouchListener() {

 

@Override

public boolean onTouch(View v, MotionEventevent) {

   Log.i(getClass().getName(), "onTouch-----" +getActionName(event.getAction()));

    mGestureDetector.onTouchEvent(event);

   //但只有返回true才能继续接收move,up等事件,也才能响应ScaleGestureDetector事件及GestureDetector中与move,up相关的事件

   return true;

    }

});

 

mGestureDetector.onTouchEvent(event);

class MyOnGestureListener extendsSimpleOnGestureListener {

@Override

public boolean onSingleTapUp(MotionEvente) {

   Log.i(getClass().getName(), "onSingleTapUp-----" +getActionName(e.getAction()));

     return false;

 }

 

 @Override

public void onLongPress(MotionEvent e) {

   Log.i(getClass().getName(), "onLongPress-----" +getActionName(e.getAction()));

}

 

@Override

public boolean onScroll(MotionEvent e1,MotionEvent e2,float distanceX,float distanceY) {

Log.i(getClass().getName(),

       "onScroll-----" + getActionName(e2.getAction()) +",(" + e1.getX() + "," + e1.getY() + ") ,("

                    + e2.getX() + ","+ e2.getY() + ")");

    return false;

}

 

@Override

public boolean onFling(MotionEvent e1,MotionEvent e2,float velocityX,float velocityY) {

 Log.i(getClass().getName(),

        "onFling-----" + getActionName(e2.getAction()) +",(" + e1.getX() + "," + e1.getY() + ") ,("

                    + e2.getX() + ","+ e2.getY() + ")");

    return false;

}

 

@Override

public void onShowPress(MotionEvent e) {

   Log.i(getClass().getName(), "onShowPress-----" +getActionName(e.getAction()));

 }

 

 @Override

 public booleanonDown(MotionEvent e) {

    Log.i(getClass().getName(), "onDown-----" +getActionName(e.getAction()));

    return false;

 

 

@Override

public boolean onDoubleTap(MotionEvent e){

   Log.i(getClass().getName(), "onDoubleTap-----" +getActionName(e.getAction()));

     return false;

 }

 

 @Override

 public booleanonDoubleTapEvent(MotionEvent e) {

 Log.i(getClass().getName(),"onDoubleTapEvent-----" + getActionName(e.getAction()));

     return false;

 }

 

 @Override

 public booleanonSingleTapConfirmed(MotionEvent e) {

    Log.i(getClass().getName(), "onSingleTapConfirmed-----" +getActionName(e.getAction()));

           return false;

       }

    }

}

这里的返回值应该是是否将点击事件消耗掉的。

对于fling手势的操作,为了让控件有fling的效果,我们是在得到fling速度之后,计算最终要滑动到的位置,然后进行移动。感觉用scroller来处理fling感觉效果好很多。

 

DragViewhelper

参考:https://www.kancloud.cn/digest/fastdev4android/109672

创建方法:

参数1.一个ViewGroup,也就是拖动子View的父控件(ViewGroup)

参数2.灵敏度一般设置成1.0f,表示灵敏度最敏感

参数3.拖拽回调,用来处理拖动的位置等相关操作

mDragHelper=ViewDragHelper.create(this, 1.0f, newDragHelperCallback());

继承ViewGragHelper.Callback类实现一个抽象方法:tryCaptureView()然后在内部进行处理需要捕获的子View(用于拖拽操作和移动)

/**

         * 进行捕获拦截,那些View可以进行drag操作

         * @param child

         * @param pointerId

         * @return  直接返回true,拦截所有的VIEW

         */

        @Override

        publicbooleantryCaptureView(Viewchild,int pointerId){

            returntrue;

        }

这样表示捕捉所有的子View,表示所有的子View都可以进行拖拽操作。

 

/**

         * 在边界滑动的时候同时滑动dragView2

         * @param edgeFlags

         * @param pointerId

         */

        @Override

        publicvoidonEdgeDragStarted(int edgeFlags, int pointerId){

          mDragHelper.captureChildView(view_two, pointerId);

        }

我们还可以这样调用tryCaptureView方法。

重写onInterceptTouchView和onTouchEvent方法来拦截事件以及让ViewDragHelper来进行处理拦截到得事件

因为ViewDragHelper的内部是根据触摸等相关事件来实现拖拽的。

/**

     * 事件分发

     * @param ev

     * @return

     */

    @Override

    publicbooleanonInterceptTouchEvent(MotionEvent ev){

       returnmDragHelper.shouldInterceptTouchEvent(ev);

    }

    @Override

    publicbooleanonTouchEvent(MotionEvent ev){

        mDragHelper.processTouchEvent(ev);

        returntrue;

    }

 

拖动行为处理

例如我们现在需要处理横向的拖拽的,那么我们需要实现clampViewPositionHorizontal方法,并且返回一个适当的数值表示横向拖拽的效果。 一般返回第二个参数即可,不过需要对边界值做一下判断这样子View不要拖出屏幕。

【注】要实现横向滑动这个方法必须要重写,因为ViewGragHelper内部该方法的时候直接返回了0,看下内部实现代码:

public intclampViewPositionHorizontal(Viewchild, int left, int dx) {

           return 0;

       }

我们重写的代码如下:

 /**

        * 水平滑动控制left

        * @param child

        * @param left

        * @param dx

        * @return

        */

       @Override

       public int clampViewPositionHorizontal(View child, int left, int dx) {

           Log.d("DragLayout","clampViewPositionHorizontal " +left + "," + dx);

           final int leftBound =getPaddingLeft();

           final int rightBound = getWidth() -view_one.getWidth();

           final int newLeft =Math.min(Math.max(left, leftBound), rightBound);

           return newLeft;

       }

这了的返回的left值就是最后拖动要到达的left值。上面的代码是防止我们拖动出显示的屏幕。

同样对于垂直方向拖拽的重写clampViewPositionVertical()基本方法也差不多如下:

 /**

        * 垂直滑动,控制top

        * @param child

        * @param top

        * @param dy

        * @return

        */

       @Override

       public int clampViewPositionVertical(View child, int top, int dy) {

           Log.d("DragLayout","clampViewPositionVertical " +top + "," + dy);

           final int topBound =getPaddingTop();

           final int bottomBound = getHeight()- view_one.getHeight();

           final int newTop =Math.min(Math.max(top, topBound), bottomBound);

           return newTop;

       }

其他方法:

dragHelper.smoothSlideViewTo(mMainContent,finalLeft,0)

这里不同于scroll,draghelper直接由自己的平滑滑动的方法,很方便。有点像scroll和scrollto+computscroll的关系,很微妙。

其实去看源码,发现dragViewHelper里面的滑动平滑实际上还是用的是scroller来实现的,所以一定要去computscroll再去处理一下。

正确结合:

if(isSmooth){            // 平滑动画//       Scroller            // 1. 触发一个平滑动画            if(dragHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)){                // true 当前动画还没有结束, 没有指定位置, 需要重绘界面.                ViewCompat.postInvalidateOnAnimation(this);            }        }else {            mMainContent.layout(finalLeft, 0, finalLeft + mWidth, 0 + mHeight);        }
}
 
在做平滑处理之后既然用到了动画和重绘,那么需要借助computscroll的力量
@Overridepublic void computeScroll() {    super.computeScroll();    if(dragHelper.continueSettling(true)){        ViewCompat.postInvalidateOnAnimation(this);    }}
 

通过判断是否是平滑动画作出的策略。一般在release的时候去做这个操作!

在这里Draghelper有一个continueSettling的方法

这个方法是:通过当前一列的动作移动我们当前捕获到的View模块。如果返回true调用者将在下一次的的框架中被调用。同时,它的内部其实是封装的scrollto方法,帮助之前的平滑移动中的scroller做真正的移动。

如果你需要调用computeScroll()方法或者类似的方法作为重绘布局的一部分,要设置它为true。

ViewCompat

在DragViewHelper中提到了ViewCompat,那么我们就把他给弄明白。

ViewCompat类主要是用来解决向下兼容性问题的。实际上在dragViewHelper中将其换做invalidate实际上并没有什么问题。为了让draghelper的动画能够向下兼容下所以还是用了ViewCompat对象。

实际上没怎么用到过,就是看demo的时候会看到有人用。所以仅仅是了解下。

常用方法

View可改变属性

可改变的属性包括:平移(setTranslationX),缩放(setScale),旋转(setRotate),透明度(setAlpha)。


computeScroll

首先需要更正下错误概念!computeScrollScroller都是计算结果来影响scrollTo,scrollBy,从而使得滑动发生改变。也就是Scroller不会调用computeScroll,反而是computeScroll调用Scroller。而computeScroll是由draw方法调用的。

@Override

    publicvoid computeScroll() {

        if(mScroller.computeScrollOffset()) {

            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());

            invalidate();

        }

    }

这里computeScrollOffset()方法判断滑动是否结束,他的内部代码通过:1、是否在滑动的标志flag;2、是否到达指定的duration;3、目标坐标是否和当前坐标一致;来判断滑动是否结束。

判断是否可移动

if(Math.abs(moveY-mDownX) >mTouchSlop&& (Math.abs(moveY-mDownY)> (Math.abs(moveX-mDownX)))){
    returntrue;
}

这个方法比较厉害!当我们做单方向滑动时防止手滑造成另外一个方向。

 

获取自定义属性

在使用自定义属性的时候,我们有两种方式拿到自定义属性

1、 将属性定义在attrs中。参考:http://blog.csdn.net/iispring/article/details/50708044

2、将属性定义在styables中。参考:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0421/2768.html

 

在attrs中的属性的使用

定义属性:

<declare-styleable name="ntm">    <attr name="haha" format="string" />    <attr name="caca" format="string" /></declare-styleable>

 

使用属性:

<reemii.a3dtouch_view.custom.SwitchCanvasBtn    android:layout_width="120dp"    android:layout_height="50dp"    android:background="@color/colorAccent"    app:slideButtonBackground="@drawable/slide_bg_btn"    app:switchState = "false"    app:haha = "haha"/>

 

对属性进行赋值:

int count = attrs.getAttributeCount();//遍历AttributeSet中的XML属性for(int i = 0; i < count; i++){    //获取attr的资源ID    int attrResId = attrs.getAttributeNameResource(i);    switch (attrResId){        case R.attr.haha:            //customText属性            attributeName = attrs.getAttributeValue(i);            break;    }}

这里的属性使用姿势需要牢记,不然感觉很容易out of index。

 

在styable中的属性的使用:

定义自定义属性:

<declare-styleable name="SlideSwitchView">    <!-- 开关背景图片,值的格式为引用 -->    <attr name="switchBackground" format="reference" />    <!-- 滑动块背景图片,值的格式为引用 -->    <attr name="slideButtonBackground" format="reference" />    <!-- 开关的状态,值的格式为boolean -->    <attr name="switchState" format="boolean" /></declare-styleable>

我之前定义在attrs中感觉找不到,然后定义到styable中就找到了。还需要看下。

使用属性:

<reemii.a3dtouch_view.custom.SwitchCanvasBtn    android:layout_width="120dp"    android:layout_height="50dp"    android:background="@color/colorAccent"    app:slideButtonBackground="@drawable/slide_bg_btn"    app:switchState = "false"/>

 

对属性赋值

public SwitchCanvasBtn(Context context, AttributeSet attrs, int defStyle) {    super(context, attrs, defStyle);    // 初始化默认的参数    initDefaultConfiguration();    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlideSwitchView);    Drawable mSlideButtonBackground = ta.getDrawable(R.styleable.SlideSwitchView_slideButtonBackground);    Drawable mSwitchBackground= ta.getDrawable(R.styleable.SlideSwitchView_switchBackground);    currentSwitchState = ta.getBoolean(R.styleable.SlideSwitchView_switchState,true);
}

 

两者的区别

其实放在哪里都无所谓,重点是看怎么获取这个很重要。目前项目还没有碰到有太多冲突的地方。

一些易混淆概念

Invalidate和postinvalidate的区别

invalidate()得在UI线程中被调动,在工作者线程中可以通过Handler来通知UI线程进行界面更新。

而postInvalidate()在工作者线程中被调用。

GetMeasureWidth()、getwidth()、getRawX()、getX()、getLeft()的区别


getWidth得到是某个view的实际尺寸。
     getMeasuredWidth是得到某view想要在parent view里面占的大小.
getMeasuredWidth():先看一下API裡面怎麼说的
The width of this view as measured in the mostrecent call to measure(). This should be used during measurement and layoutcalculations only.

得到的是在最近一次调用measure()方法测量后得到的view的宽度,它仅仅用在测量和layout的计算中。
所以此方法得到的是view的内容佔据的实际宽度。

getWidth(): View在设定好布局后整个View的宽度
 getMeasuredWidth(): 对View上的内容进行测量后得到的View内容佔据的宽度,前提是你必须在父布局的onLayout()方法或者此View的onDraw()方法裡调 用measure(0,0);(measure 参数的值你可以自己定义),否则你得到的结果和getWidth()得到的结果一样。
     这两个方法的区别就是看你有没有用measure()方法,当然measure()的位置也是很重要的。


其实上面几个得到的数据的位置也是不一样的。

getMeasuredWidth是在ommeasure获得的。

getWidth()方法要在layout()过程结束后才能获取到,他的返回值是mRight-mLeft


getScrollX()getScrollY()

getScrollX():返回的是左边滑动之后的x坐标位置;返回值是pixels

getScrollY():返回的是顶部滑动之后的y坐标位置,返回值是pixels

是个坐标意义的值。

 

onmeasure中的widthMeasureSpec

int类型占有32位,它将其高2位作为mode,30为作为size这样用32位就解决了sizemode的问题。

Eg:

mode是EXACTLY,而size=101(5)那么size+mode的值为:


假如是自定义一个View的话,测量一下其大小就行了,如果是ViewGroup呢,则需要遍历其所有的子View来,并为每个子View测量它的大小

在测量子View时需要两个参数,measureWidthmeasureHeight这两个值是根布局传过来的,也就是说是父View和子View本身共同决定子View的大小。

 

dispatchdraw和ondraw的区别

draw过程会调用onDraw(Canvas canvas)方法,然后就是dispatchDraw(Canvas canvas)方法, dispatchDraw()主要是分发给子组件进行绘制,我们通常定制组件的时候重写的是onDraw()方法。值得注意的是ViewGroup容器组件的绘制,当它没有背景时直接调用的是dispatchDraw()方法,而绕过了draw()方法,当它有背景的时候就调用draw()方法,而draw()方法里包含了dispatchDraw()方法的调用。因此要在ViewGroup上绘制东西的时候往往重写的是dispatchDraw()方法而不是onDraw()方法。

 

自定义控件绘制步骤

setContentView(resourceID)过程


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

0 0