自定义View简单案例-自绘控件

实现的效果

继承View

继承ViewGroup实现一个顶栏

下面来实现上面效果
自定义View基础Android开发之自定义View基础

继承View

需要自定义绘制内容,需要继承View,必须要重写onDraw方法,在onDraw方法中来进行绘制,实现onMeasure方法,来测量控件的空间。

创建类,继承View或View的子类,并提供相关的构造方法
public class SimpleView extends View {

    public SimpleView(Context context) {
        super(context);
    }

    public SimpleView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }
}
重写onMeasure()方法,并调用setMeasuredDimension(int width, int height)设置控件的大小(因为Demo只是简单的画了几个图形,所以没重写)
重写onDraw()方法,实现绘制特定内容
@Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
}
重写onTouchEvent()方式处理触摸事件

此处不作处理

@Override
public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
}
开始绘制

1.说到绘制,当然需要画笔了。初始化画笔

    /**
     * 初始化画笔
     */
    private void initPaint() {
        // 定义画笔
        paint = new Paint();
        // 设置画笔的字体大小
        paint.setTextSize(100);
        // 线条的样式 - 粗体 斜线
        paint.setTypeface(Typeface.DEFAULT_BOLD);
        paint.setStrokeWidth(10);
        // 设置画笔的颜色
        paint.setColor(Color.RED);
        // 设置画笔的样式
        // Paint.Style.STROKE - 边线样式 - 圆(空心圆)
        // Paint.Style.FILL - 填充样式 - 圆(实心圆)
        paint.setStyle(Paint.Style.STROKE);
        //paint.setStyle(Paint.Style.FILL);
        // 是否抗锯齿
        paint.setAntiAlias(true);
    }

2.绘制图形

    /**
     * 用于绘制
     * 当初始化View(在代码中添加自定义控件,或者在布局中添加加载时),此方法会调用
     *
     * @param canvas - 画布
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /****************绘制内容***************/
        // 绘制线(float startX, float startY, float stopX, float stopY, Paint paint)
        canvas.drawLine(100, 100, 600, 300, paint);
        // 绘制圆(float cx, float cy, float radius, Paint paint)
        canvas.drawCircle(500, 500, 200, paint);
        // 绘制文字(String text, float x, float y, Paint paint)
        canvas.drawText("绘制内容", 450, 750, paint);
        // 绘制矩形1(float left, float top, float right, float bottom, Paint paint)
        canvas.drawRect(400, 950, 600, 1100, paint);
        
        // 绘制矩形2
        RectF rect = new RectF();
        rect.left = 400;
        rect.top = 950;
        rect.right = 600;
        rect.bottom = 1100;
        //canvas.drawRect(rect,paint);
    }
在布局文件中使用<类全名>并设置属性(或在Java代码中使用)
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
    tools:context="com.rair.customview.SimpleViewActivity">

    <com.rair.customview.view.SimpleView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</android.support.constraint.ConstraintLayout>
运行
完整代码
public class SimpleView extends View {

    // 声明画笔
    private Paint paint;

    // 创建构造方法
    public SimpleView(Context context) {
        //super(context);
        // 调用当前类的其他的构造方法
        this(context, null);
    }

    /**
     * 如果在布局中添加自定义控件,则需要添加如下构造方法
     *
     * @param context      - 上下文
     * @param AttributeSet set - 属性对象,它包含这当前控件所有的属性
     */
    public SimpleView(Context context, AttributeSet set) {
        super(context, set);
        initPaint();
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        // 定义画笔
        paint = new Paint();
        // 设置画笔的字体大小
        paint.setTextSize(100);
        // 线条的样式 - 粗体 斜线
        paint.setTypeface(Typeface.DEFAULT_BOLD);
        paint.setStrokeWidth(10);
        // 设置画笔的颜色
        paint.setColor(Color.RED);
        // 设置画笔的样式
        // Paint.Style.STROKE - 边线样式 - 圆(空心圆)
        // Paint.Style.FILL - 填充样式 - 圆(实心圆)
        paint.setStyle(Paint.Style.STROKE);
        //paint.setStyle(Paint.Style.FILL);
        // 是否抗锯齿
        paint.setAntiAlias(true);
    }

    /**
     * 用于绘制
     * 当初始化View(在代码中添加自定义控件,或者在布局中添加加载时),此方法会调用
     *
     * @param canvas - 画布
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /****************绘制内容***************/
        // 绘制线
        canvas.drawLine(100, 100, 600, 300, paint);
        // 绘制圆
        canvas.drawCircle(500, 500, 200, paint);
        // 绘制文字
        canvas.drawText("绘制内容", 450, 750, paint);
        // 绘制矩形
        RectF rect = new RectF();
        rect.left = 400;
        rect.top = 950;
        rect.right = 600;
        rect.bottom = 1100;
        //canvas.drawRect(rect,paint);
        canvas.drawRect(400, 950, 600, 1100, paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }
}
继承ViewGroup
  • 绘制,需要绘制控件,重写onDraw方法
  • 通过在ViewGroup中通过this.addView(<控件对象>)来添加控件
  • onMeasure方法,来测量控件的空间
  • onLayout方法必须实现,在此方法中处理子控件的位置。当视图初始化,或者视图位置发生改变时候,调用此方法。
创建类,继承ViewGroup
public class GroupView extends ViewGroup {
    public GroupView(Context context) {
        this(context, null);
    }

    public GroupView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}
继承的ViewGroup,通过addView添加子View

效果中左为Imageiew,中间是TextView

    private ImageView imageView;
    private TextView tvTitle;

    /**
     * 添加子控件
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void addViews(Context context) {
        imageView = new ImageView(context);
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        imageView.setBackground(backDrawable);
        LayoutParams layoutParam = new LayoutParams(100, 100);
        imageView.setLayoutParams(layoutParam);

        // 添加一个ImageView
        this.addView(imageView);
        // 添加一个TextView
        tvTitle = new TextView(context);
        tvTitle.setTextSize(20);
        tvTitle.setText(title);
        tvTitle.setTextColor(titleColor);
        this.addView(tvTitle);
    }
重写onMeasure()方法,并调用setMeasuredDimension(int width, int height)设置控件的大小
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 要测量子控件的高度和宽度
        // 计算子控件
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        // 先计量控件的宽度
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int measureWidth = 0;
        // 确切大小 500dp 或者 match_parent
        if (widthMode == MeasureSpec.EXACTLY) {
            measureWidth = MeasureSpec.getSize(widthMeasureSpec);
        }
        // wrap_content
        else if (widthMode == MeasureSpec.AT_MOST) {
            // 设置屏幕的宽度
            measureWidth = screenWidth;
        }
        // 高度计算,获取最高的控件
        int measureHeight = 0;
        int childCount = this.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = this.getChildAt(i);
            // child.getMeasuredHeight()能够获取到具体的控件大小
            // 必须先调用measureChildren(widthMeasureSpec,heightMeasureSpec);
            // 判断哪个控件的高度最大,来作为视图容器的高度
            if (child.getMeasuredHeight() > measureHeight) {
                measureHeight = child.getMeasuredHeight();
            }
        }
        setMeasuredDimension(measureWidth, measureHeight + 50);
    }
  • int MeasureSpec.getMode(mSpec) 获取控件大小模式
    • MeasureSpec.EXACTLY,确切空间,布局中的属性值一般为match_parent或确切固定的值(如指定了160dp)
    • MeasureSpec.AT_MOST 尽量多的空间,布局中的属性值wrap_content
    • MeasureSpec.UNSPECIFIED 未指定的,一般在父控件中使用
  • MeasureSpec.getSize(mSpec) 获取控件大小
添加自定义View属性(使控件更灵活)
<resources>
    <declare-styleable name="GroupView">
        <!-- 背景 -->
        <attr name="nav_bg" format="color|reference" />
        <!-- 标题颜色 -->
        <attr name="title_color" format="color|reference" />
        <!-- 标题文字 -->
        <attr name="title_text" format="string|reference" />
        <!-- 标题文字大小 -->
        <attr name="title_size" format="dimension|reference" />
        <!-- 返回图标 -->
        <attr name="back_image" format="reference" />
    </declare-styleable>
</resources>
解析自定义属性
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.GroupView);
        title = array.getString(R.styleable.GroupView_title_text);
        bgColor = array.getColor(R.styleable.GroupView_nav_bg, 0x0);
        titleColor = array.getColor(R.styleable.GroupView_title_color, 0xF);
        backDrawable = array.getDrawable(R.styleable.GroupView_back_image);
        titleSize = array.getDimensionPixelSize(R.styleable.GroupView_title_size, 10);
        array.recycle();
        // 设置背景颜色
        this.setBackgroundColor(bgColor);
        // 初始化画笔
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(bgColor);
实现onLayout方法(俗称定位,就是确定每个子View的位置)

onLayout方法必须实现,在此方法中处理子控件的位置。当视图初始化,或者视图位置发生改变时候,调用此方法。

具体按需求计算

    /**
     * 当视图初始化,或者视图位置发生改变时候
     *
     * @param changed 是否改变
     * @param l 左
     * @param t 上
     * @param r 右
     * @param b 下
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //getWidth()
        // 需要设置子控件显示的位置
        //getChildAt(0).layout();
        // 控件的宽度
        int viewWidth = getMeasuredWidth();
        int viewHeight = getMeasuredHeight();
        int left = 0, top = 0, right = 0, bottom = 0;
        int childCount = this.getChildCount();
        View view = null;
        for (int i = 0; i < childCount; i++) {
            view = this.getChildAt(i);
            if (i == 0) {
                left = view.getMeasuredWidth() / 3;
            } else if (i == 1) {
                left = (viewWidth - view.getMeasuredWidth()) / 2;
            }
            top = (viewHeight - view.getMeasuredHeight()) / 2;
            right = left + view.getMeasuredWidth();
            bottom = top + view.getMeasuredHeight();
            view.layout(left, top, right, bottom);
        }
    }
添加回调接口(返回按钮点击事件)
    public interface OnFinishListener {
        void onFinish();
    }

    private OnFinishListener listener;

    public void setOnFinishListener(OnFinishListener listener) {
        this.listener = listener;
    }
在布局文件中使用<类全名>并设置属性(或在Java代码中使用)
<?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"
    tools:context="com.rair.customview.GroupViewActivity">

    <com.rair.customview.view.GroupView
        android:id="@+id/m_groupview"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        app:back_image="@drawable/ic_arrow_back_black_24dp"
        app:nav_bg="@color/colorAccent"
        app:title_color="@color/white"
        app:title_size="25sp"
        app:title_text="首页" />

    <com.rair.customview.view.GroupView
        android:id="@+id/m_groupview1"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:layout_marginTop="10dp"
        app:back_image="@drawable/ic_arrow_back_black_24dp"
        app:nav_bg="@color/colorAccent"
        app:title_color="@color/white"
        app:title_size="25sp"
        app:title_text="首页" />
</LinearLayout>

设置事件监听(点击返回退出Activity)

public class GroupViewActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_group_view);

        GroupView groupView = (GroupView) findViewById(R.id.m_groupview);
        groupView.setOnFinishListener(new GroupView.OnFinishListener() {
            @Override
            public void onFinish() {
                finish();
            }
        });
    }
}
运行

Demo已上传

GitHub:https://github.com/Rairmmd/RairDemo/tree/master/CustomViewDemo
Coding:https://git.coding.net/Rair/RairDemo.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值