关于自定义view 以及view中的动画实现

来源:互联网 发布:网络问卷有什么问题 编辑:程序博客网 时间:2024/05/22 17:22

最近研究了一下自定义view,现在记一下笔记:看的人不如自己去google developer自己学
自定义view用于定制特殊效果的view,或者在不愿意嵌套多个view(布局)时候实现复杂布局,
首先需要继承View类,或者如果功能比较接进已经有了的view,比如LinearLayout 可以直接继承它,然后实现自己功能;
继承之后,需要实现其构造方法,主要是用于建立内置的变量,可以在xml布局中进行设置的那种,这里掠过,另外,一些初始化也需要在这里做,主要是Paint 对象的初始化,最好不要在ondraw时候初始Paint对象,因为会造成卡顿问题;
通过重写 onMesure 方法,指定view的实际可用的大小,因为他的参数中给出了相应的宽度,也就是xml中设置的大小,需要通过MeasureSpec.getSize 取出,单位是px ,如果想要设置控件大小,需要用工具类将dp转化为px 然后进行设置

   int h = MeasureSpec.getSize(heightMeasureSpec);   int w = MeasureSpec.getSize(widthMeasureSpec);

获取大小是在onSizeChange 方法中获取的

  //用于设定内容大小 ,因为这个参数会显示实际的大小    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);//        Log.e(TAG, "onSizeChanged: " + "w :" + w + "   h:" + h);  //获取到的是实际的宽高变化        //rect 是画矩形使用的东西        mRect = new Rect(0, 0, 150, 150);  //画出来的不是实际大小,而在onmesure设置的是实际大小        mWidth = w;        mHeight = h;        mMipmapRect = new Rect(50, 50, 100, 100);    }

然后主要的是ondraw 方法,用于绘制界面:主要能够绘制 圆 线 矩形 图像,
其中绘制图像还是比较繁琐一点的,需要借助工具类 Metrix 类对图像做出 位移 旋转 缩放等操作,还可以通过cavas 设置其颜色的渐近变化 和 多个图像之间的遮盖关系,这都是与图层相关的问题:

   LinearGradient mshader=new LinearGradient(50,50,mWidth,mHeight,Color.RED,Color.BLUE,        Shader.TileMode.CLAMP);//长度在sizechangge时候才能获取   mLinePaint.setShader(mshader);

这是颜色渐近变化,还可以使用repeat 镜像 等几种模式
图层遮盖使用 xformode 方法,具体没用过,可以用来实现圆角图片。。
通过rect 类加载图片可以实现图片的剪切。
主要说的是动画的两种实现方式:
主要都是通过ondraw 的不停重绘实现的,也就是调用 postInvalidate(),先要说一下对手势滑动的接收问题:先要重写ontouch方法,拿到回调,然后通过Detector 类进行动作的处理:

  //需要从ontouchevent 中对滑动事件进行关联    @Override    public boolean onTouchEvent(MotionEvent event) {        boolean result = mDetector.onTouchEvent(event);        return result;    }
  /*    集成手势感应的方式     */    class mListener extends GestureDetector.SimpleOnGestureListener {        @Override        public boolean onDown(MotionEvent e) {            return true;        }        @Override        public boolean onSingleTapUp(MotionEvent e) {            return true;        }        @Override        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {//             mScroller.startScroll((int)e1.getX(),(int)e1.getY(),(int)e2.getX(),(int)e2.getY());//            postInvalidate();            return true;        }        @Override        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {            //模仿滑动的时候的滚动            mScroller.fling((int) e1.getX(), (int) e1.getY(), (int) velocityX/4, (int) velocityY/4, 0, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);            //操作1 的方式是通过view的重绘实现的,因为onfling 的调用了好几次 所以重绘了好几次 然后就有了动画效果            if (Build.VERSION.SDK_INT >= 11) {                //valueanimator 是对值的平滑过渡                mScrollAnimator.setDuration(mScroller.getDuration());                mScrollAnimator.start();            }else{            //第一种动画            postInvalidate();//滑动之后进行更新UI的操作   通过重绘进行操作的步骤             }            return true;        }    }

这里onDown 方法必须实现,并且必须返回true,否则不能接受到任何回调
然后必须说一下Scroller 类,这个类是个工具类,具体作用,模拟滑动 拖动 或者fling 后不同时间的位置,通过getCurX 和getCurY 来得到一定时间的位置,用于模拟现实世界的变化,这是动画重要的一个类,尤其是在实现手势动作的时候。
第一种方式:通过ondraw 时候调用位置变化:

 if(!mScroller.isFinished()){            mScroller.computeScrollOffset();            mRect.offsetTo(mScroller.getCurrX()-75,mScroller.getCurrY()-75);            Log.e(TAG, "x is :"+mScroller.getCurrX() );            Log.e(TAG, "y is : "+mScroller.getCurrY() );        }        if (!mScroller.isFinished()) {            postInvalidate();        }

调用 mScroller.computeScrollOffset(); 会计算出时间内的一个位置 ,没结束就调用刷新继续计算,很直观
第二种方式:通过属性动画实现 ValueAnimator:
这个类主要作用是在一定时间内不停的进行回调,类似于一个闹钟,可以设置间隔和速度,然后调用方法,但是重绘仍然需要自己处理,它不做其他的事,只有值的变化:
在init 方法中:

  mScrollAnimator=ValueAnimator.ofFloat(0,1);        mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                if (!mScroller.isFinished()) {                    mScroller.computeScrollOffset();                    //计算动画位置                    mRect.offsetTo(mScroller.getCurrX()-75,mScroller.getCurrY()-75);                    postInvalidate();                } else {                    if (Build.VERSION.SDK_INT >= 11) {                        mScrollAnimator.cancel();                    }                }            }        });

这样就不停重绘,改变了位置,Scroller 类很重要,比如fling 这种操作,Scroller 会帮助计算出速度之后,算出某个时间的位置,以及结束之后滑动的总长度。
旋转是通过setRotate 实现的,不过会改变整个view的旋转。。旋转角度算法自己实现

public class TestView extends View {    private Paint mPaint;    private Paint mTextPaint;    private Paint mLinePaint;    private Paint bitmapPaint;    private Bitmap mBitmap;//要画出来的bitmap对象    private int drawableWidth;    private int drawbleHeight;    private Paint circlePaint;    private GestureDetector mDetector;    //处理图形    Matrix mMatrix;    String TAG = "view";    int mWidth;    int mHeight;    Rect mMipmapRect;    private int angle = 0;    //获取到滑动的数据    private Scroller mScroller;    // 动画使用类    private ValueAnimator mScrollAnimator;    /*    集成手势感应的方式     */    class mListener extends GestureDetector.SimpleOnGestureListener {        @Override        public boolean onDown(MotionEvent e) {            return true;        }        @Override        public boolean onSingleTapUp(MotionEvent e) {            return true;        }        @Override        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {//             mScroller.startScroll((int)e1.getX(),(int)e1.getY(),(int)e2.getX(),(int)e2.getY());//            postInvalidate();            return true;        }        @Override        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {            //e1 起点的x y; e2 当前点的位置坐标信息  x y 都是坐标信息            float scrollTheta = vectorToScalarScroll(                    velocityX,                    velocityY,                    e2.getX() - mWidth / 2,                    e2.getY() - mHeight / 2);//            Log.e(TAG, "e1 x :" + e1.getX());//            Log.e(TAG, "e1 y :" + e1.getY());//            Log.e(TAG, "e2 x :" + e2.getX());//            Log.e(TAG, "e2 y :" + e2.getY());//            Log.e(TAG, "velocity X :" + velocityX);//            Log.e(TAG, "velocityY  :" + velocityY);            //模仿滑动的时候的滚动            mScroller.fling((int) e1.getX(), (int) e1.getY(), (int) velocityX/4, (int) velocityY/4, 0, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);//            postInvalidate();//滑动之后进行更新UI的操作   通过重绘进行操作的步骤            //操作1 的方式是通过view的重绘实现的,因为onfling 的调用了好几次 所以重绘了好几次 然后就有了动画效果            if (Build.VERSION.SDK_INT >= 11) {                //valueanimator 是对值的平滑过渡                mScrollAnimator.setDuration(mScroller.getDuration());                mScrollAnimator.start();            }            return true;        }    }    //需要从ontouchevent 中对滑动事件进行关联    @Override    public boolean onTouchEvent(MotionEvent event) {        boolean result = mDetector.onTouchEvent(event);        return result;    }    public void setAngle(int angle) {        this.angle = angle;        invalidate();        requestLayout();    }    public TestView(Context context) {        super(context);        init();    }    public TestView(Context context, @Nullable AttributeSet attrs) {        super(context, attrs);        init();    }    public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {        super(context, attrs, defStyleAttr, defStyleRes);        init();    }    //初始化paint    private void init() {        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaint.setColor(getResources().getColor(android.R.color.holo_blue_bright));        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//设置抗锯齿的效果 假如我要我的文字的位置在哪        mTextPaint.setColor(getResources().getColor(android.R.color.black));        mTextPaint.setTextSize(DensityUtils.sp2px(getContext(), 12));//        mTextPaint.setShader();  设置颜色的渐进        //设置线条 颜色的渐变 渐变起点(像素 x,y)终点(x,y) 起始颜色 ,终点颜色,渐变模式        //clamp 渐变        mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);//        mLinePaint.setColor(Color.RED);//        mLinePaint.setShader(mshader);        mLinePaint.setStyle(Paint.Style.STROKE);        mLinePaint.setStrokeWidth(DensityUtils.dp2px(getContext(), 4));        //画图        bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.indicator_arrow);//拿到bitmap的对象        drawableWidth = mBitmap.getWidth();        drawbleHeight = mBitmap.getHeight();        bitmapPaint = new Paint();        mMatrix = new Matrix();        //画圆        circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);        circlePaint.setStyle(Paint.Style.STROKE);        mDetector = new GestureDetector(TestView.this.getContext(), new mListener());        //根据当前的版本创建Scroller 对象        if (Build.VERSION.SDK_INT < 11) {            mScroller = new Scroller(getContext());        } else {            mScroller = new Scroller(getContext(), null, true);        }        /**         * 通过animation 动画实现 和通过 重绘进行实现的原理都是一样的 都调用了重绘         * 这个动画作用是只要不调用停止   mScrollAnimator.cancel(); 就会一直有回调         * 然后在回调里面不停改变绘制参数,对里面空间进行位置上面的重绘         * 并没有调用view对象之类的东西, 不比直接在ondraw里面直观,但是更合理 (官方说的)         * duaration  就是不停的调用的时间          *         */        mScrollAnimator=ValueAnimator.ofFloat(0,1);        mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                if (!mScroller.isFinished()) {                    mScroller.computeScrollOffset();                    //计算动画位置                    mRect.offsetTo(mScroller.getCurrX()-75,mScroller.getCurrY()-75);                    postInvalidate();                } else {                    if (Build.VERSION.SDK_INT >= 11) {                        mScrollAnimator.cancel();                    }                }            }        });    }    //实际画出来的大小是由 setMeasuredDimension 设定的 根据设定获取到设定的宽高,然后将其画出来    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int h = MeasureSpec.getSize(heightMeasureSpec);        int w = MeasureSpec.getSize(widthMeasureSpec);//        Log.e(TAG, "onMeasure: " + "with=" + w + "   " + "height=" + h);        //这样获取的就是像素点,也就是实际的像素大小,不是dp  需要将dp转化为px  然后运算//        width=getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec); // 也可以这么设置宽高//        height=getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec);        setMeasuredDimension(w, h);  //设置宽高, onsizechangge 时候显示出大小    }    //最后执行的ondraw 不需要太在意效率    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        setRotation(30);        canvas.drawRect(mRect, mPaint);        /*         画出文字和直线         *///        canvas.drawText("hello world", 50, 50, mTextPaint);//        //设置渐变//        LinearGradient mshader=new LinearGradient(50,50,mWidth,mHeight,Color.RED,Color.BLUE, Shader.TileMode.CLAMP);//长度在sizechangge时候才能获取//        mLinePaint.setShader(mshader);//        canvas.drawLine(50, 50, mWidth, mHeight, mLinePaint);        /*        画出bitmap         *///        canvas.drawBitmap(mBitmap,0,0,bitmapPaint);        //其实是剪切图形是时候比较有用//        canvas.drawBitmap(mBitmap,new Rect(20,20,160,160),new Rect(40,40,150,150),bitmapPaint);//          mMatrix.//        mMatrix.setTranslate(mWidth / 2, mHeight / 2);//图片平移  确定位置////        //先旋转之后再从x 宽度上面进行拉伸两倍,高度不变//        mMatrix.postScale(1, 2, mWidth / 2, mHeight / 2);//        mMatrix.postRotate(angle, mWidth / 2, mHeight / 2);//图片根据角度1和初始位置进行旋转//        canvas.drawBitmap(mBitmap, mMatrix, bitmapPaint);////        //画圆//        canvas.drawCircle(mWidth/2,mHeight/2,DensityUtils.dp2px(getContext(),70),circlePaint);//        tickScrollAnimation();        /**scroller 是个能够模拟滑动的类,根据滑动会给出一系列的值 然后进行界面重绘         * 通过scroller 类进行重绘         * 原理很直观,每次调用后重新得到scroller的位置         *         *///        if(!mScroller.isFinished()){//            mScroller.computeScrollOffset();//            mRect.offsetTo(mScroller.getCurrX()-75,mScroller.getCurrY()-75);//            Log.e(TAG, "x is :"+mScroller.getCurrX() );//            Log.e(TAG, "y is : "+mScroller.getCurrY() );//        }//        if (!mScroller.isFinished()) {//            postInvalidate();//        }        /**         * 通过动画操作运动         * 通过动画也能够实现,但是实现原理??         *  动画类会在设定的时间内不停的回调 然后就会执行重新绘制         */    }//    private void tickScrollAnimation() {//        if (!mScroller.isFinished()) {//            mScroller.computeScrollOffset();//            setPieRotation(mScroller.getCurrY());////        } else {//           if (Build.VERSION.SDK_INT >= 11) {//               mScrollAnimator.cancel();//            }//            onScrollFinished();////        }//    }    Rect mRect;    //用于设定内容大小 ,因为这个参数会显示实际的大小    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);//        Log.e(TAG, "onSizeChanged: " + "w :" + w + "   h:" + h);  //获取到的是实际的宽高变化        //rect 是画矩形使用的东西        mRect = new Rect(0, 0, 150, 150);  //画出来的不是实际大小,而在onmesure设置的是实际大小        mWidth = w;        mHeight = h;        mMipmapRect = new Rect(50, 50, 100, 100);    }    /**     * 旋转的工具类 计算旋转位置     * Helper method for translating (x,y) scroll vectors into scalar rotation of the pie.     *     * @param dx The x component of the current scroll vector.     * @param dy The y component of the current scroll vector.     * @param x  The x position of the current touch, relative to the pie center.     * @param y  The y position of the current touch, relative to the pie center.     * @return The scalar representing the change in angular position for this scroll.     */    private static float vectorToScalarScroll(float dx, float dy, float x, float y) {        // get the length of the vector        float l = (float) Math.sqrt(dx * dx + dy * dy);        // decide if the scalar should be negative or positive by finding        // the dot product of the vector perpendicular to (x,y).        float crossX = -y;        float crossY = x;        float dot = (crossX * dx + crossY * dy);        float sign = Math.signum(dot);        return l * sign;    }}
原创粉丝点击