Android

Android自定义View

2021-02-25  本文已影响0人  Timmy_zzh
当Android SDK中提供的系统UI控件无法满足业务需求时,就需要考虑自己实现UI控件

1.继承现有控件

效果图:

1.自定义View-继承自系统控件.png

代码:

/**
 * 自定义ToolBar:
 * 左边返回图片,中间文本,右边图片
 * 1。获取自定义属性
 * 2。设置 文本与左右图片的位置
 */
public class CustomeToolbar extends RelativeLayout {

    private String myTitleText;
    private int myTitleTextColor;
    private float myTitleTextSize;
    private Drawable leftImgDrawable;
    private Drawable rightImgDrawable;

    public CustomeToolbar(Context context, AttributeSet attrs) {
        super(context, attrs);
        //准备控件
        ImageView leftImg = new ImageView(context);
        ImageView rightImg = new ImageView(context);
        leftImg.setImageDrawable(leftImgDrawable);
        rightImg.setImageDrawable(rightImgDrawable);
        TextView titleTextView = new TextView(context);
        titleTextView.setText(myTitleText);
        titleTextView.setTextColor(myTitleTextColor);
        titleTextView.setTextSize(myTitleTextSize);

        //添加控件,并设置排放规则
        this.addView(leftImg, leftImgParams);
        this.addView(rightImg, rightImgParams);
        this.addView(titleTextView, titleParams);
    }
}

2.自定义属性

2.1.attrs.xml文件中声明自定义属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CustomeToolbar">
        <!--居中的文本-->
        <attr name="myTitleText" format="string|reference" />
        <!--文本颜色-->
        <attr name="myTitleTextColor" format="color|reference" />
        <!--文本大小-->
        <attr name="myTitleTextSize" format="dimension|reference" />
        <!--左边图片-->
        <attr name="lefeImgSrc" format="reference" />
        <!--右边图片-->
        <attr name="rightImgSrc" format="reference" />
    </declare-styleable>
</resources>
2.2.在xml不仅文件中使用自定义属性
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.timmy.demopractice.view.CustomeToolbar
        android:layout_width="match_parent"
        android:layout_height="68dp"
        app:lefeImgSrc="@mipmap/ic_back"
        android:background="#ffff00"
        app:myTitleText="自定义View"
        app:myTitleTextColor="#ff0000"
        app:myTitleTextSize="10sp"
        app:rightImgSrc="@mipmap/ic_launcher" />

</LinearLayout>

2.3.在CustomeToolbar中,获取自定义属性的引用值
    private void initAttr(Context context, AttributeSet attrs) {
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CustomeToolbar);
        myTitleText = ta.getString(R.styleable.CustomeToolbar_myTitleText);
        myTitleTextColor = ta.getColor(R.styleable.CustomeToolbar_myTitleTextColor, Color.BLACK);
        //已由sp转换为px
        myTitleTextSize = ta.getDimension(R.styleable.CustomeToolbar_myTitleTextSize, 14);
        leftImgDrawable = ta.getDrawable(R.styleable.CustomeToolbar_lefeImgSrc);
        rightImgDrawable = ta.getDrawable(R.styleable.CustomeToolbar_rightImgSrc);
        ta.recycle();
    }
    public CustomeToolbar(Context context, AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.xxx, this);
    }

3.直接继承自View或者ViewGroup

3.1.onDraw
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

Canvas

public class Canvas extends BaseCanvas {
  void drawArc(RectF oval, startAngle, float sweepAngle, useCenter,Paint paint) 绘制弧形
    void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) 绘制图片
    void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) 绘制圆形
    void drawLine(float startX, float startY, float stopX, float stopY,Paint paint) 绘制直线
    void drawOval(@NonNull RectF oval, @NonNull Paint paint) 绘制椭圆
    void drawPath(@NonNull Path path, @NonNull Paint paint) 绘制path路径
    void drawPoint(float x, float y, @NonNull Paint paint) 绘制点
    void drawRect(Rect r, @NonNull Paint paint) 绘制矩形区域
    void drawRoundRect(RectF rect, float rx, float ry, Paint paint) 绘制圆角矩形
    void drawText(String text, float x, float y, @NonNull Paint paint) 绘制文本
}

调用Canvas类的draw方法最终会调用BaseCanvas中的native方法

Paint

public class Paint {
    void setStyle(Style style) 设置绘制模式
    void setColor(@ColorInt int color) 设置画笔颜色
    void setAlpha(int a) 设置画笔透明度
    void setStrokeWidth(float width) 设置线条宽度
    void setStrokeCap(Cap cap) 设置画笔绘制两端时的样式
    void setStrokeJoin(Join join) 设置画笔绘制时,折线的样式
    Shader setShader(Shader shader) 设置Paint的填充效果
    ColorFilter setColorFilter(ColorFilter filter) 设置画笔线的样式
    public Xfermode setXfermode(Xfermode xfermode) 设置画笔的层叠效果
    public Typeface setTypeface(Typeface typeface) 设置字体样式
    void setTextSize(float textSize) 设置文本字体大小
    void setAntiAlias(boolean aa) 设置抗锯齿开关
    void setDither(boolean dither) 设置防抖动开关
}
实现圆环进行条控件
  1. 自定义控件
/**
 * 绘制扇形进度控件:绘制一个圆,和其中代表进度的扇形
 * 1。接收自定义属性- 原的颜色,扇形的颜色等
 * 2。初始化Paint(2)
 * 3。onDraw方法中绘制圆形和扇形
 * 在onSizeChange方法中获取到绘制的区域
 */
public class PieImageView extends View {

    private Paint arcPaint;
    private Paint circlePaint;
    private RectF mBound;
    private int radius;

    public PieImageView(Context context) {
        this(context, null);
    }

    public PieImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PieImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint(context);
    }

    private void initPaint(Context context) {
        arcPaint = new Paint();
        arcPaint.setAntiAlias(true);
        arcPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        arcPaint.setStrokeWidth(dpTopx(0.1f, context));
        arcPaint.setColor(Color.BLUE);

        circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        circlePaint.setStyle(Paint.Style.STROKE);
        circlePaint.setStrokeWidth(dpTopx(2, context));
        circlePaint.setColor(Color.RED);

        mBound = new RectF();
    }

    /**
     * 拿到控件的宽高
     * 设置圆形绘制的半径
     * 设置圆形绘制的区域 mBound
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.e("Tim", "onSizeChanged w:" + w + " ,h:" + h);
        int min = Math.min(w, h);
        radius = min / 3;
        mBound.set(min / 2 - radius, min / 2 - radius, min / 2 + radius, min / 2 + radius);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.e("Tim", "onDraw");
        canvas.drawCircle(mBound.centerX(), mBound.centerY(), radius, circlePaint);
        canvas.drawArc(mBound, 0, 125, true, arcPaint);
    }

    float density = 0;

    /**
     * dp转px
     */
    private int dpTopx(float dp, Context context) {
        if (density == 0) {//密度
            density = context.getResources().getDisplayMetrics().density;
        }
        Log.e("Tim", "density:" + density);
        return (int) (dp * density);
    }
}

  1. 在xml中使用
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
    <com.timmy.demopractice.view.PieImageView
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="@color/colorAccent" />

</LinearLayout>
  1. 最后效果图
2.自定义View固定宽高.png
当自定义PieImageView的宽高设置为wrap_content的时候,控件展示效果如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.timmy.demopractice.view.PieImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent" />

</LinearLayout>
3.自定义View-包裹宽高.png
3.2.onMeasure
自定义View为什么需要重新测量
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
MeasureSpec
    //宽度测量模式
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    //宽度测量大小
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    //高度测量模式
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    //高度测量大小
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
自定义PieImageView处理
public class View implements ... {
  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), 
                                            widthMeasureSpec),
                             getDefaultSize(getSuggestedMinimumHeight(),
                                                    heightMeasureSpec)
                            );
    }

    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }
  
    protected int getSuggestedMinimumHeight() {
        return (mBackground == null) ? mMinHeight :
      max(mMinHeight,   mBackground.getMinimumHeight());
    }
}

代码修改,处理onMeasure方法:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //宽度测量模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        //宽度测量大小
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        //高度测量模式
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //高度测量大小
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (MeasureSpec.AT_MOST == widthMode || MeasureSpec.AT_MOST == heightMode) {
            int size = Math.min(widthSize, heightSize);
            setMeasuredDimension(size, size);
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }

最终效果:

4.自定义View-复写onMeasure方法.png
上一篇 下一篇

猜你喜欢

热点阅读