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

最近研究了一下自定义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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值