About Custom view and Animation Implementation in view

Keywords: xml Android Google Attribute

Now that I've studied customizing view s recently, I'll take a note: People might as well go to google developer and learn by themselves
Custom views are used to customize views for special effects or to implement complex layouts when you are unwilling to nest multiple views.
First, you need to inherit the View class, or if a feature comparison is incorporated into an existing view, such as LinearLayout, which can be directly inherited, then implement its own function.
After inheritance, you need to implement its construction method, which is mainly used to build built-in variables, which can be set in the xml layout, skipped here, and some initialization needs to be done here, mainly the initialization of the Paint object. It is better not to start the Paint object at ondraw, because it will cause Carton problems.
By overriding the onMesure method, you specify the actual size available for the view, because the width given in his parameters, that is, the size set in the xml, needs to be taken out by MeasureSpec.getSize, in px. If you want to set the size of the control, you need to convert dp to PX with a tool class and set it

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

Get size is obtained in the onSizeChange method

  //Used to set the size of the content because this parameter displays the actual size
    @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); //Get the actual width change
        //rect is what draws rectangles
        mRect = new Rect(0, 0, 150, 150);  //Instead of drawing the actual size, onmesure sets the actual size
        mWidth = w;
        mHeight = h;
        mMipmapRect = new Rect(50, 50, 100, 100);

    }

Then there is the ondraw method, which is used to draw the interface: it is mainly able to draw circular rectangular images.
Drawing an image is tedious, you need to use the tool class Metrix to do displacement, rotation, scaling and other operations on the image, you can also set the asymptotic change of its color and the occlusion relationship between multiple images through cavas, which are layer-related issues:

   LinearGradient mshader=new LinearGradient(50,50,mWidth,mHeight,Color.RED,Color.BLUE,        Shader.TileMode.CLAMP);//Length is only available when sizechangge
   mLinePaint.setShader(mshader);

This is an asymptotic change in color and several other modes can be used, such as repeat mirroring
Layer masking uses the xformode method, which is unused and can be used to implement rounded pictures.
Loading pictures through the rect class allows you to cut pictures.
The main point is that there are two ways to achieve animation:
Mainly through ondraw's continuous redrawing, that is, calling postInvalidate(), first talk about the reception of gesture sliding: first override the ontouch method, get callbacks, and then use the Detector class to process actions:

  //Slide events need to be associated from ontouchevent
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean result = mDetector.onTouchEvent(event);

        return result;
    }
  /*
    How to integrate gesture sensing
     */
    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) {

            //Imitate scrolling while sliding
            mScroller.fling((int) e1.getX(), (int) e1.getY(), (int) velocityX/4, (int) velocityY/4, 0, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);

            //Operation 1 is done by redrawing the view because onfling is redrawn several times and then animated
            if (Build.VERSION.SDK_INT >= 11) {
                //valueanimator is a smooth transition to values
                mScrollAnimator.setDuration(mScroller.getDuration());
                mScrollAnimator.start();
            }else{
            //First animation
            postInvalidate();//Action to update UI after sliding Steps to action by redrawing
             }
            return true;
        }
    }

The onDown method must be implemented here and must return true or no callbacks will be accepted
Then you have to talk about the Scroller class, which is a tool class that simulates the position of a sliding drag or fling at different times. It uses getCurX and getCurY to get the position of a certain time to simulate changes in the real world. This is an important class of animation, especially when implementing gesture actions.
The first way is to call a location change when using 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();
        }

Call mScroller.computeScrollOffset(); calculate a position in time, call refresh to continue calculation before end, it is intuitive
Second way: ValueAnimator is implemented through attribute animation:
The main function of this class is to make callbacks continuously for a certain period of time, similar to an alarm clock, which can set the interval and speed, then call the method, but the redrawing still needs to be done by itself. It does nothing but change the value:
In the init method:

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


                }
            }
        });

This keeps redrawing and changing positions, and the Scroller class is important, such as fling, which helps calculate the speed, the position at a time, and the total length of the slide after the end.
Rotation is achieved by setRotate, but it changes the rotation of the entire view.Rotation angle algorithm self-implementation

public class TestView extends View {
    private Paint mPaint;
    private Paint mTextPaint;
    private Paint mLinePaint;
    private Paint bitmapPaint;
    private Bitmap mBitmap;//bitmap object to draw
    private int drawableWidth;
    private int drawbleHeight;
    private Paint circlePaint;

    private GestureDetector mDetector;
    //work with graphics
    Matrix mMatrix;
    String TAG = "view";
    int mWidth;
    int mHeight;
    Rect mMipmapRect;
    private int angle = 0;
    //Get Sliding Data
    private Scroller mScroller;
    // Animation Usage Class
    private ValueAnimator mScrollAnimator;



    /*
    How to integrate gesture sensing
     */
    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) {
            //X y of e1 starting point; x y of e2 current point is coordinate information
            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);
            //Imitate scrolling while sliding
            mScroller.fling((int) e1.getX(), (int) e1.getY(), (int) velocityX/4, (int) velocityY/4, 0, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);

//            PosInvalidate(); //Action to update UI after sliding Steps to action by redrawing

            //Operation 1 is done by redrawing the view because onfling is redrawn several times and then animated
            if (Build.VERSION.SDK_INT >= 11) {
                //valueanimator is a smooth transition to values
                mScrollAnimator.setDuration(mScroller.getDuration());
                mScrollAnimator.start();
            }
            return true;
        }
    }

    //Slide events need to be associated from 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();
    }

    //Initialize 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);//Set Anti-aliasing Effect If I want where my text is
        mTextPaint.setColor(getResources().getColor(android.R.color.black));
        mTextPaint.setTextSize(DensityUtils.sp2px(getContext(), 12));
//        mTextPaint.setShader(); sets the gradation of colors
        //Set gradient for line colors Start (pixel x,y) End (x,y) Start color, End color, Gradient mode
        //clamp gradient

        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));
        //Drawing
        bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.indicator_arrow);//Object to get bitmap
        drawableWidth = mBitmap.getWidth();
        drawbleHeight = mBitmap.getHeight();
        bitmapPaint = new Paint();
        mMatrix = new Matrix();

        //Draw a circle
        circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        circlePaint.setStyle(Paint.Style.STROKE);
        mDetector = new GestureDetector(TestView.this.getContext(), new mListener());

        //Create a Scroller object from the current version
        if (Build.VERSION.SDK_INT < 11) {
            mScroller = new Scroller(getContext());
        } else {
            mScroller = new Scroller(getContext(), null, true);
        }
        /**
         * Redraw is invoked in the same way as animation and redraw
         * This animation works as long as the stop mScrollAnimator.cancel() is not called; there will always be callbacks
         * Then in the callback, the drawing parameters are constantly changed to redraw the interior space above the position
         * There's nothing like calling a view object, it's no more intuitive than directly inside an ondray, but it makes sense (officially)
         * duaration  Is the time of non-stop invocation
          *
         */

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


                }
            }
        });


    }

    //The actual size drawn is based on the setMeasuredDimension setting to get the set width and height and then draw it

    @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);
        //The result is a pixel point, which is the actual pixel size, not that dp needs to be converted to px and then computed

//        width=getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec); //You can also set the width and height this way
//        height=getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec);
        setMeasuredDimension(w, h);  //Set width and height to show size when onsizechangge
    }

    //The last ondraw doesn't need to care too much about efficiency
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        setRotation(30);
        canvas.drawRect(mRect, mPaint);

        /*
         Draw text and lines
         */
//        canvas.drawText("hello world", 50, 50, mTextPaint);
//        //Set Gradient
//        LinearGradient mshader=new LinearGradient(50,50,mWidth,mHeight,Color.RED,Color.BLUE, Shader.TileMode.CLAMP); //Length is available at sizechange time
//        mLinePaint.setShader(mshader);
//        canvas.drawLine(50, 50, mWidth, mHeight, mLinePaint);
        /*
        Draw a bitmap
         */
//        canvas.drawBitmap(mBitmap,0,0,bitmapPaint);
        //It's actually time to cut graphics
//        canvas.drawBitmap(mBitmap,new Rect(20,20,160,160),new Rect(40,40,150,150),bitmapPaint);
//          mMatrix.

//        mMatrix.setTranslate(mWidth / 2, mHeight / 2); //Picture panning determines location
//
//        //Rotate first, then stretch twice the width of x, with the same height
//        mMatrix.postScale(1, 2, mWidth / 2, mHeight / 2);
//        mMatrix.postRotate(angle, mWidth / 2, mHeight / 2); //Picture rotates according to angle 1 and initial position
//        canvas.drawBitmap(mBitmap, mMatrix, bitmapPaint);
//
//        //Draw a circle
//        canvas.drawCircle(mWidth/2,mHeight/2,DensityUtils.dp2px(getContext(),70),circlePaint);
//        tickScrollAnimation();

        /**scroller Is a class that can simulate sliding, based on which a series of values are given and the interface is redrawn
         * Redraw with scroller class
         * The principle is intuitive, getting the scroller's location back after each call
         *
         */
//        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();
//        }
        /**
         * Manipulating motion through animation
         * Animation is possible, but how?
         *  The animation class calls back continuously for a set time and then redraws
         */


    }
//    private void tickScrollAnimation() {
//        if (!mScroller.isFinished()) {
//            mScroller.computeScrollOffset();
//            setPieRotation(mScroller.getCurrY());
//
//        } else {
//           if (Build.VERSION.SDK_INT >= 11) {
//               mScrollAnimator.cancel();
//            }
//            onScrollFinished();
//
//        }
//    }
    Rect mRect;

    //Used to set the size of the content because this parameter displays the actual size
    @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); //Get the actual width change
        //rect is what draws rectangles
        mRect = new Rect(0, 0, 150, 150);  //Instead of drawing the actual size, onmesure sets the actual size
        mWidth = w;
        mHeight = h;
        mMipmapRect = new Rect(50, 50, 100, 100);

    }


    /**
     * Rotate Tool Class Calculates Rotation Position
     * 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;
    }
}

Posted by mickeyunderscore on Thu, 23 May 2019 09:34:07 -0700