Android自定义控件的实现

辛苦堆砌,转载请注明出处,谢谢!

本文主要讲讲如何派生View类,实现Android自定义控件。通过派生View实现自定义控件首先要清楚两个步骤:测量与绘制。


自定义控件首先要为Android框架提供其内容的测量值,这个阶段在onMeasure方法中进行,我们可以重写该方法,提供测量值。测量阶段发生在绘制之前,在测量期间,视图还没有确切的大小,只有侧量尺寸。如果在分配大小后还需要做处理,那就要重写onSizeChanged方法。


测量阶段onMeasure方法会传入两个参数,分别是宽和高对应的MeasureSpec的整型值,它有三个值:

AT_MOST:当我们的布局参数使用match_parent或者其他给出大小上限的参数值时,会传入该模式,我们的onMeasure应当返回对应的上限值。

EXACTLY:当布局参数是固定值时,会传入该模式,我们的onMeasure应当返回对应的宽和高的确切值。

UNSPECIFIED:当视图无约束时,会传入该模式,onMeasure应当返回宽和高都为0.


完成测量阶段后,会进入绘制阶段,这个阶段需要重写onDraw方法,传入的参数是Android框架按照测量阶段的数值创建的Canvas实例,我们基于该实例进行绘制即可。


下面是一个例子,实现了一个倾斜45度的TextView,它可以按照文本大小调整自己的宽高,然后和父视图边框接合,效果如图


下面看看代码,首先是我们的视图类

package com.yjp.rotationtextview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.TextView;

public class RotationTextView extends TextView {

    private Drawable mBackground;

    public RotationTextView(Context context) {
        //调用自己的构造函数
        this(context, null);
    }

    public RotationTextView(Context context, AttributeSet attrs) {
        //调用自己的构造函数
        this(context, attrs, 0);
    }

    public RotationTextView(Context context, AttributeSet attrs, int defStyle) {
        //调用超类TextView的构造函数
        super(context, attrs, defStyle);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取文本的宽度,以计算倾斜45度后的长度,注意计算宽度和高度的代码不同
        int textWidth = (int) getPaint().measureText(getText().toString(), 0, getText().length());
        int min = (int) (textWidth / Math.cos(Math.PI / 4));

        //调用resolveSizeAndState()快捷方法计算尺寸
        int w = resolveSizeAndState(min, widthMeasureSpec, 0);
        int h = resolveSizeAndState(min, heightMeasureSpec, 0);
        int result = Math.max(w, h);

        //必须调用
        setMeasuredDimension(result, result);
    }

    @SuppressWarnings("deprecation")
    @Override
    protected void onDraw(Canvas canvas) {
        //设置绘制背景使用的Paint
        Paint backgroundPaint = new Paint();
        backgroundPaint.setAntiAlias(true);
        backgroundPaint.setStyle(Paint.Style.FILL);

        if (mBackground != null) {
            backgroundPaint.setColor(((ColorDrawable)mBackground).getColor());
        } else {
            backgroundPaint.setColor(getContext().getResources().getColor(android.R.color.white));
        }

        //获取字符串高度,注意计算宽度和高度的代码不同
        Paint.FontMetrics fm = getPaint().getFontMetrics();
        double textHeight = Math.ceil(fm.descent - fm.top);

        //绘制梯形外边框,并填充颜色
        Path backgroundPath = new Path();
        backgroundPath.moveTo(0, getHeight() - (int) textHeight - 20);
        backgroundPath.lineTo(0, getHeight());
        backgroundPath.lineTo(getWidth(), 0);
        backgroundPath.lineTo(getWidth() - (int) textHeight - 20, 0);
        backgroundPath.close();
        canvas.drawPath(backgroundPath, backgroundPaint);

        //绘制文本
        RectF backgroundRectF = new RectF();
        backgroundPath.computeBounds(backgroundRectF, false);
        int textWidth = (int) getPaint().measureText(getText().toString(), 0, getText().length());
        double delta = textWidth / 2 * Math.cos(Math.PI / 4);
        Path textPath = new Path();
        textPath.moveTo((float)(backgroundRectF.centerX() - delta - 5),
                (float)(backgroundRectF.centerY() + delta - 5));
        textPath.lineTo((float)(backgroundRectF.centerX() + delta - 5),
                (float)(backgroundRectF.centerY() - delta - 5));
        canvas.drawTextOnPath(getText().toString(), textPath, 0, 0, getPaint());
    }

    //需要重载一系列background相关的函数,否则TextView默认用该background填充整个正方形视图
    //所有函数最终调用的都是setBackgroundDrawable()
    @SuppressWarnings("deprecation")
    @Override
    public void setBackgroundResource(int resid) {
        Drawable d = null;
        if (resid != 0) {
            d = getContext().getResources().getDrawable(resid);
        }
        this.setBackground(d);
    }

    @Override
    public void setBackgroundColor(int color) {
        if (mBackground instanceof ColorDrawable) {
            ((ColorDrawable) mBackground.mutate()).setColor(color);
        } else {
            this.setBackground(new ColorDrawable(color));
        }
    }

    @Override
    public void setBackground(Drawable background) {
        this.setBackgroundDrawable(background);
    }

    @SuppressWarnings("deprecation")
    @Override
    public void setBackgroundDrawable(Drawable background) {
        mBackground = background;
    }
}
重点在onMeasure和onDraw方法上,另外为了能够支持视图设置background属性,我们重写了setBackgroundxxx的一系列方法。下面是布局

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_green_light"
    tools:context="com.yjp.rotationtextview.MainActivity">

    <com.yjp.rotationtextview.RotationTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light"
        android:text="测试文本1"
        android:textSize="20sp"
        android:textColor="@android:color/holo_red_dark"/>

    <com.yjp.rotationtextview.RotationTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_blue_bright"
        android:text="测试文本2"
        android:textSize="40sp"
        android:textColor="@android:color/holo_red_dark"/>

    <com.yjp.rotationtextview.RotationTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_purple"
        android:text="测试文本3"
        android:textSize="60sp"
        android:textColor="@android:color/holo_red_dark"/>

</FrameLayout>




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值