Android开发之自定义view进行旋转动画

老套路先上图:

整个view非常简单,我自定义view里面都有详细的注释说明

先看自定义view部分代码:

package cn.xiayiye5.customizestudy.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import cn.xiayiye5.customizestudy.R;

/**
 * @author : xiayiye5
 * @date : 2021/3/18 15:50
 * 类描述 :自定义view - 画圆基础练习
 */
public class BasicView extends View {
    private int measuredWidth;
    private int measuredHeight;
    private Paint paint;
    private float paintWidth;
    private int paintColor;
    private int paintStroke;
    private Paint textPaint;
    private final float textWidth;
    private final int textColor;
    private final float textSize;

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public BasicView(Context context) {
        this(context, null);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public BasicView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public BasicView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public BasicView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BasicView);
        paintWidth = typedArray.getDimension(R.styleable.BasicView_paint_width, 10);
        paintColor = typedArray.getColor(R.styleable.BasicView_paint_color, Color.GREEN);
        paintStroke = typedArray.getInt(R.styleable.BasicView_paint_stroke, 1);
        //获取文字颜色,宽度,大小等
        textWidth = typedArray.getDimension(R.styleable.BasicView_text_width, 10);
        textColor = typedArray.getColor(R.styleable.BasicView_text_color, Color.GREEN);
        textSize = typedArray.getDimension(R.styleable.BasicView_text_size, 20);
        typedArray.recycle();
        initView();
    }

    private void initView() {
        //文字用到的画笔
        textPaint = new Paint();
        //圆用到的画笔
        paint = new Paint();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //设置中间文字颜色大小宽度等
        textPaint.setTextSize(textSize);
        textPaint.setStrokeWidth(textWidth);
        textPaint.setColor(textColor);
        //设置画笔圆的大小颜色宽度等
        paint.setColor(paintColor);
        paint.setTextSize(14f);
        paint.setStrokeWidth(paintWidth);
        if (paintStroke == StrokeModel.STROKE_ZERO) {
            paint.setStyle(Paint.Style.FILL);
        } else if (paintStroke == StrokeModel.STROKE_ONE) {
            paint.setStyle(Paint.Style.STROKE);
        } else if (paintStroke == StrokeModel.STROKE_TWO) {
            paint.setStyle(Paint.Style.FILL_AND_STROKE);
        } else {
            throw new NumberFormatException("The value is only 0(BasicView.StrokeModel.STROKE_ZERO) or 1(BasicView.StrokeModel.STROKE_ONE) or 2(BasicView.StrokeModel.STROKE_TWO)");
        }
        float length = textPaint.measureText("扬宏豕慧");
        float fontHeight = getFontHeight(textPaint);
        //写文字在坐标中心居中
        canvas.drawText("扬宏豕慧", (measuredWidth - length) / 2f, (measuredHeight + fontHeight / 2f) / 2f, textPaint);
        canvas.drawCircle(measuredWidth / 2f, measuredHeight / 2f, (measuredWidth - paintWidth) / 2f, paint);
        //画圆的横线
        canvas.drawLine(0, measuredHeight / 2f, measuredWidth, measuredHeight / 2f, paint);
        //画圆的竖线
        canvas.drawLine(measuredWidth / 2f, 0, measuredHeight / 2f, measuredHeight, paint);
    }

    /**
     * @return 返回指定的文字高度
     */
    public float getFontHeight(Paint paint) {
        Paint.FontMetrics fm = paint.getFontMetrics();
        //文字基准线的下部距离-文字基准线的上部距离 = 文字高度
        return fm.descent - fm.ascent;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int height = 0;
        int width = 0;
        width = setViewMode(widthMeasureSpec, width);
        height = setViewMode(heightMeasureSpec, height);
        //获取测量模式后设置下即可
        setMeasuredDimension(width, height);
        measuredWidth = getMeasuredWidth();
        measuredHeight = getMeasuredHeight();
        Log.e("打印宽高", measuredWidth + "-" + measuredHeight);
    }

    /**
     * 获取测量模式的方法
     *
     * @param measureSpec 测量模式
     * @param viewSize    view的大小
     * @return 返回 view经过测量后的大小
     */
    private int setViewMode(int measureSpec, int viewSize) {
        int viewMode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);
          if (viewMode == MeasureSpec.AT_MOST) {
            // wrap_content模式 测量规格模式:子级可以任意大到指定的大小。
            viewSize = getMeasuredWidth() / 3;
        } else if (viewMode == MeasureSpec.EXACTLY) {
            // 已经设置了具体大小,类似10dp 和match_parent 测量规格模式:父级已确定子级的确切大小。不管孩子想要多大,都要给他这些限制。
            viewSize = size;
        } else if (viewMode == MeasureSpec.UNSPECIFIED) {
            //设置多大就多大可以无限大 度量规范模式:父级未对子级施加任何约束。它可以是它想要的任何尺寸。
            viewSize = size;
        }
        return viewSize;
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            //获取view在当前屏幕的坐标
            float rawX = event.getRawX();
            float rawY = event.getRawY();
            //获取view在自身的坐标
            float x = event.getX();
            float y = event.getY();
            Log.e("打印屏幕坐标", rawX + "," + rawY);
            Log.e("打印view坐标y", x + "," + y);
            if (x > 0 && y > 0) {
                //证明点击了此view
                Toast.makeText(getContext(), "点击了此布局(" + x + "," + y + ")", Toast.LENGTH_LONG).show();
                clickViewListener.clicked(this);
            }
        }
        return true;
    }

    public static class StrokeModel {
        private StrokeModel() {
        }

        public static final int STROKE_ZERO = 0;
        public static final int STROKE_ONE = 1;
        public static final int STROKE_TWO = 2;
    }

    public interface ClickViewListener {
        /**
         * 点击了哪个view
         *
         * @param view 具体的view
         */
        void clicked(View view);
    }

    ClickViewListener clickViewListener;

    public void setClickViewListener(ClickViewListener clickViewListener) {
        this.clickViewListener = clickViewListener;
    }

    /**
     * 设置画笔圆的宽度
     *
     * @param paintWidth 宽度
     */
    public void setPaintWidth(float paintWidth) {
        this.paintWidth = paintWidth;
        requestLayout();
    }

    /**
     * 设置画笔圆的颜色
     *
     * @param paintColor 颜色
     */
    public void setPaintColor(int paintColor) {
        this.paintColor = paintColor;
        invalidate();
    }

    /**
     * 设置画笔圆的模式
     *
     * @param paintStroke 模式
     */
    public void setPaintStroke(int paintStroke) {
        this.paintStroke = paintStroke;
        requestLayout();
    }
}

再看layout布局代码

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <cn.xiayiye5.customizestudy.view.BasicView
        android:id="@+id/bv_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:paint_color="@color/purple_500"
        app:paint_stroke="stroke"
        app:paint_width="1dp"
        app:text_color="@color/purple_200"
        app:text_size="16sp"
        app:text_width="10dp" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="changeColor"
        android:text="改变颜色"
        app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

再看activity页面代码

package cn.xiayiye5.customizestudy.ui.activity;

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import java.util.Random;

import cn.xiayiye5.customizestudy.R;
import cn.xiayiye5.customizestudy.view.BasicView;
import cn.xiayiye5.customizestudy.view.BasicView.StrokeModel;

/**
 * @author : xiayiye5
 * @date : 2021/3/19 11:36
 * 类描述 :
 */
public class BasicActivity extends AppCompatActivity {
    int num = 0;
    BasicView bvView;
    private ObjectAnimator rotation;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_basic);
        bvView = findViewById(R.id.bv_view);
        bvView.setClickViewListener(view -> Toast.makeText(BasicActivity.this, "点击了此布局", Toast.LENGTH_LONG).show());
    }

    public void changeColor(View view) {
        if (num % StrokeModel.STROKE_TWO == 0) {
            //开始执行动画
            startAnim();
            Toast.makeText(this, "彩虹红灯开启", Toast.LENGTH_SHORT).show();
            handler.sendEmptyMessageDelayed(0, 500);
        } else {
            Toast.makeText(this, "彩虹红灯关闭", Toast.LENGTH_SHORT).show();
            handler.removeCallbacksAndMessages(null);
            //关闭动画
            rotation.end();
        }
        num++;
    }

    /**
     * 开始动画的方法
     */
    private void startAnim() {
        ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(bvView, "alpha", 0.0f, 1.0f);
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(bvView, "scaleX", 0.0f, 1.0f);
        ObjectAnimator scaleY = ObjectAnimator.ofFloat(bvView, "scaleY", 0.0f, 1.0f);
        rotation = ObjectAnimator.ofFloat(bvView, "rotation", 0.0f, 1080f);
        rotation.setRepeatCount(100);
        //旋转不停顿
        rotation.setInterpolator(new LinearInterpolator());
        //重复次数
        rotation.setDuration(1000);
        rotation.start();
//        AnimatorSet set = new AnimatorSet();
//        set.playTogether(alphaAnim, scaleX, scaleY);
//        set.setDuration(3000);
//        set.start();
    }

    private final Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            Log.e("打印", "发送消息");
            bvView.setPaintColor(Color.rgb(new Random().nextInt(255), new Random().nextInt(255), new Random().nextInt(255)));
            bvView.setPaintStroke(StrokeModel.STROKE_ONE);
            bvView.setPaintWidth(new Random().nextInt(30));
            //继续发送形成循环模式
            sendEmptyMessageDelayed(0, 500);
        }
    };

    @Override
    protected void onPause() {
        super.onPause();
        handler.removeCallbacksAndMessages(null);
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (num > 0) {
            //继续发送形成循环模式
            handler.sendEmptyMessageDelayed(0, 500);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        handler.removeCallbacksAndMessages(null);
    }
}

在看下attrs.xml属性文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="BasicView">
        <!--画笔颜色-->
        <attr name="paint_color" format="color" />
        <!--画笔宽度-->
        <attr name="paint_width" format="dimension" />
        <!--画笔形状-->
        <attr name="paint_stroke" format="enum">
            <enum name="fill" value="0" />
            <enum name="stroke" value="1" />
            <enum name="fill_and_stroke" value="2" />
        </attr>
        <!--文字颜色-->
        <attr name="text_color" format="color" />
        <!--文字大小-->
        <attr name="text_size" format="dimension" />
        <!--文字宽度-->
        <attr name="text_width" format="dimension" />
    </declare-styleable>
</resources>

整个理偶成非常简单说下具体步骤:

先通过attires文件自定义属性=>然后绘制view将自定义属性通过layout文件与attrs的自定义属性对应设置相应的属性值即可。其它就是API相关的东西了。

感谢博主提供动画结束方法:博主直达

关于view的测量模式可以查看Google官方文档的说明:Google官方文档说明

猜你喜欢

转载自blog.csdn.net/xiayiye5/article/details/115178080
今日推荐