自定义控件(27)---自定义控件之组合控件(2) 通用的类似设置界面的样子

一个APP中类似如下的界面
这里写图片描述

我们可以通过如下的做法来实现:
1、最基础的就是堆砌Xml布局文件
2、然后随着经验的积累,就开始用到自定义组合控件,通过获取attr里面的东东,然后去通过代码的方式去构造里面的控件,不过没有很好的扩展性,可以参考我的一篇博客链接
自定义控件(25)—自定义控件之组合控件
3、依然是获取attr里面的东西,通过移动canvas画布的方式,去构建里面的各个控件,这个方法扩展性更好

今天我们就来学习第三种用法的使用,let’s begin
首先看看demo的ui图
这里写图片描述

对应布局我就直接贴出代码了,
activity_main.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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <com.safly.ui.ItemView
        style="@style/SetItemStyleBase"
        android:background="@drawable/click_back"
        app:drawableStart="@drawable/option1"
        app:textStart="@string/personal"
        app:textStartLeftMargin="10dp" />
    <include layout="@layout/devider" />

    <com.safly.ui.ItemView
        style="@style/SetItemStyleBase"
        android:background="@drawable/click_back"
        app:drawableStart="@drawable/option1"
        app:drawableStartHeight="30dp"
        app:drawableStartWidth="30dp"
        app:textStart="@string/personal"
        app:textStartLeftMargin="10dp" />
    <include layout="@layout/devider" />

    <com.safly.ui.ItemView
        style="@style/SetItemStyleBase"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="@drawable/click_back"
        android:paddingLeft="20dp"
        android:paddingRight="20dp"
        app:arrowDrawable="@drawable/arrow"
        app:arrowHeight="14dp"
        app:arrowWidth="8dp"
        app:drawableStart="@drawable/option3"
        app:itemStyle="arrow"
        app:textStart="@string/virtual"
        app:textStartColor="#FF3E96"
        app:textStartLeftMargin="10dp"
        app:textStartSize="20sp" />
</LinearLayout>

click_back.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" android:drawable="@android:color/holo_blue_light"/>
    <item android:state_pressed="false" android:drawable="@android:color/white"/>
</selector>

我们看看attrs.xml里面都需要定义什么东东?

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ItemView">
        <attr name="itemStyle" format="enum">
            <enum name="normal" value="0"/>
            <enum name="arrow" value="1"/>
        </attr>

        <attr name="drawableStart" format="reference"/>
        <attr name="drawableStartWidth" format="dimension"/>
        <attr name="drawableStartHeight" format="dimension"/>

        <attr name="textStart" format="string"/>
        <attr name="textStartColor" format="color"/>
        <attr name="textStartSize" format="dimension"/>
        <!--绘制textStart时距离左边的偏移量-->
        <attr name="textStartLeftMargin" format="dimension"/>

        <attr name="arrowDrawable" format="reference"/>
        <attr name="arrowWidth" format="dimension"/>
        <attr name="arrowHeight" format="dimension"/>
    </declare-styleable>
</resources>

我们就直接看下ItemView是如何构造的把

package com.safly.ui;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;

import com.safly.R;

/**
 * Created by Administrator on 2016/11/10.
 */
public class ItemView extends View {
    private static final String TAG = "ItemView";
    private DisplayMetrics dm;
    private static final int STYLE_NORMAL = 0;
    private static final int STYLE_ARROW = 1;
    private int style = STYLE_NORMAL;

    private Drawable drawableStart;
    private int drawableStartWidth = -2,drawableStartHeight = -2;


    private String textStart = null;
    private int textStartColor = DEFAULT_TEXTCOLOR;
    private static final int DEFAULT_TEXTCOLOR = Color.parseColor("#5a5a5a");
    private static final int DEFAULT_TEXTSIZE = 14;
    private int textStartSize;
    private int textStartLeftMargin = 0;


    private Drawable arrowDrawable;
    private int arrowWidth = -2,arrowHeight = -2;

    /***toggleButton默认宽*/
    private static final int TOGGLEBUTTON_WIDTH = 8;
    /***toggleButton默认高*/
    private static final int TOGGLEBUTTON_HEIGHT = 14;


    //默认控件的宽,匹配屏幕大小
    private static final int DEFAULT_WIDTH = -1;
    //默认控件的高度
    private static final int DEFAULT_HEIGHT = 50;

    private TextPaint mTextStartPaint;
    public ItemView(Context context) {
        this(context,null);
    }

    public ItemView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public ItemView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        dm = Resources.getSystem().getDisplayMetrics();
        setEnabled(true);
        setClickable(true);
        setLongClickable(false);
        setup(attrs);
    }


    //初始化view的属性
    private void setup(AttributeSet attrs){

        TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.ItemView);
        if(ta != null){
            try {

                style = ta.getInt(R.styleable.ItemView_itemStyle,STYLE_NORMAL);

                drawableStart = ta.getDrawable(R.styleable.ItemView_drawableStart);
                if(drawableStart != null){
                    drawableStartWidth = ta.getDimensionPixelSize(
                            R.styleable.ItemView_drawableStartWidth,applyDimension(drawableStartWidth));
                    drawableStartHeight = ta.getDimensionPixelSize(
                            R.styleable.ItemView_drawableStartHeight,applyDimension(drawableStartHeight));
                }

                textStart = ta.getString(R.styleable.ItemView_textStart);

                Log.i(TAG,"textStart---------"+textStart);
                textStartColor = ta.getColor(R.styleable.ItemView_textStartColor, textStartColor);
                textStartSize = ta.getDimensionPixelSize(
                        R.styleable.ItemView_textStartSize, applyDimension(DEFAULT_TEXTSIZE));
                textStartLeftMargin = ta.getDimensionPixelOffset(
                        R.styleable.ItemView_textStartLeftMargin, textStartLeftMargin);
                Log.i(TAG,"textStart---------"+textStartLeftMargin);

                if(style == STYLE_ARROW){
                    arrowDrawable = ta.getDrawable(R.styleable.ItemView_arrowDrawable);
                    arrowWidth = ta.getDimensionPixelSize(
                            R.styleable.ItemView_arrowWidth,applyDimension(TOGGLEBUTTON_WIDTH));
                    arrowHeight = ta.getDimensionPixelSize(
                            R.styleable.ItemView_arrowHeight,applyDimension(TOGGLEBUTTON_HEIGHT));
                }
            }catch (Exception e){
                Log.e(TAG, "setup: e", e);
            }finally {
                ta.recycle();
            }
        }
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        /**计算宽**/
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        if(widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST){
            width = applyDimension(DEFAULT_WIDTH);
        }

        /**计算高**/
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (heightMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.AT_MOST) {
            height = applyDimension(DEFAULT_HEIGHT);
        }

        Log.i(TAG, "[onMeasure] width = " + width + ",height = " + height);
        setMeasuredDimension(width, height);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        drawStartDrawable(canvas);
        drawTextStart(canvas);
        switch (style){
            case STYLE_ARROW:
                drawArrowDrawable(canvas);
                break;
        }
    }
    /**
     * 绘制drawableStart
     * @param canvas
     */
    private void drawStartDrawable(Canvas canvas){
        Log.d(TAG, "drawStartDrawable: ");
        if (drawableStart != null){
            mesureDrawableStartSize();
            final float startY = (getHeight() - drawableStartHeight) / 2.0f;
            canvas.save();
            canvas.translate(getPaddingLeft(),startY);
            drawableStart.setBounds(0,0,drawableStartWidth,drawableStartHeight);
            drawableStart.draw(canvas);
            canvas.restore();
        }
    }
    private void mesureDrawableStartSize(){
        drawableStartWidth =
                drawableStartWidth >=0?drawableStartWidth:drawableStart.getIntrinsicWidth();
        drawableStartHeight =
                drawableStartHeight >= 0?drawableStartHeight:drawableStart.getIntrinsicHeight();
    }
    /**
     * 绘制textStart
     * @param canvas
     *ascent = ascent线的y坐标 - baseline线的y坐标;//负数

    descent = descent线的y坐标 - baseline线的y坐标;//正数

    top = top线的y坐标 - baseline线的y坐标;//负数

    bottom = bottom线的y坐标 - baseline线的y坐标;//正数

    leading = top线的y坐标 - ascent线的y坐标;//负数
    ----------------------------------------
    ascent线Y坐标 = baseline线的y坐标 + fontMetric.ascent;

    descent线Y坐标 = baseline线的y坐标 + fontMetric.descent;

    top线Y坐标 = baseline线的y坐标 + fontMetric.top;

    bottom线Y坐标 = baseline线的y坐标 + fontMetric.bottom;
     */
    private void drawTextStart(Canvas canvas){
        Log.d(TAG, "drawTextStart: ");
        assumeTextStartPaint();
        Paint.FontMetrics fontMetrics = mTextStartPaint.getFontMetrics();
        final int left = getPaddingLeft()+drawableStartWidth+textStartLeftMargin;
        RectF target = new RectF(left,0,left+mTextStartPaint.measureText(textStart),getHeight());
        //(fontMetrics.bottom+fontMetrics.top) = top线Y+bottom线Y-2*baseLine线Y
        int baseLine =  (int)((target.bottom + target.top - fontMetrics.bottom - fontMetrics.top) / 2.0f);
        canvas.save();
        canvas.translate(left,baseLine);
        canvas.drawText(textStart, 0, 0, mTextStartPaint);
        canvas.restore();
    }
    private void assumeTextStartPaint(){
        if (mTextStartPaint == null){
            final Resources res = getResources();
            mTextStartPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
            mTextStartPaint.density = res.getDisplayMetrics().density;
            mTextStartPaint.setTextSize(textStartSize);
            mTextStartPaint.setColor(textStartColor);
            mTextStartPaint.setAntiAlias(true);
        }
    }

    private void drawArrowDrawable(Canvas canvas){
        Log.d(TAG, "drawArrowDrawable: ");
        mesureDrawablerrowSize();
        final float startY = (getHeight() - arrowHeight) / 2.0f;
        canvas.save();
        canvas.translate(getWidth() - getPaddingRight(),startY);
        arrowDrawable.setBounds(0,0,arrowWidth,arrowHeight);
        arrowDrawable.draw(canvas);
        canvas.restore();
    }
    private void mesureDrawablerrowSize(){
        arrowWidth = arrowWidth >= 0?arrowWidth:arrowDrawable.getIntrinsicWidth();
        arrowHeight = arrowHeight >= 0?arrowHeight:arrowDrawable.getIntrinsicHeight();
    }

    /**
     * dp2px
     * px是安卓系统内部使用的单位
     * @param value
     */
    private int applyDimension(float value){
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, dm);
    }
}

接下来对上面几个重要的方法进行解释说明:
在onDraw里面是需要计算用心计算的, drawStartDrawable(canvas);这是画左边的图片

 private void drawStartDrawable(Canvas canvas){
        Log.d(TAG, "drawStartDrawable: ");
        if (drawableStart != null){
            mesureDrawableStartSize();
            final float startY = (getHeight() - drawableStartHeight) / 2.0f;
            canvas.save();
            canvas.translate(getPaddingLeft(),startY);
            drawableStart.setBounds(0,0,drawableStartWidth,drawableStartHeight);
            drawableStart.draw(canvas);
            canvas.restore();
        }
    }
    private void mesureDrawableStartSize(){
        drawableStartWidth =
                drawableStartWidth >=0?drawableStartWidth:drawableStart.getIntrinsicWidth();
        drawableStartHeight =
                drawableStartHeight >= 0?drawableStartHeight:drawableStart.getIntrinsicHeight();
    }

以上就是获取图片的宽度、高度,然后就去计算图片的getPaddingLeft,
(getHeight() - drawableStartHeight) / 2.0f 这是计算红线的高度
这里写图片描述
然后通过drawableStart.setBounds(0,0,drawableStartWidth,drawableStartHeight);
为图片设定区域,最后画出来即可

对应第三个的小箭头是同理

我们就看下第二个文字是如何处理的?

   private void drawTextStart(Canvas canvas){
        Log.d(TAG, "drawTextStart: ");
        assumeTextStartPaint();
        Paint.FontMetrics fontMetrics = mTextStartPaint.getFontMetrics();
        final int left = getPaddingLeft()+drawableStartWidth+textStartLeftMargin;
        RectF target = new RectF(left,0,left+mTextStartPaint.measureText(textStart),getHeight());
        //(fontMetrics.bottom+fontMetrics.top) = top线Y+bottom线Y-2*baseLine线Y
        int baseLine =  (int)((target.bottom + target.top - fontMetrics.bottom - fontMetrics.top) / 2.0f);
        canvas.save();
        canvas.translate(left,baseLine);
        canvas.drawText(textStart, 0, 0, mTextStartPaint);
        canvas.restore();
    }
    private void assumeTextStartPaint(){
        if (mTextStartPaint == null){
            final Resources res = getResources();
            mTextStartPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
            mTextStartPaint.density = res.getDisplayMetrics().density;
            mTextStartPaint.setTextSize(textStartSize);
            mTextStartPaint.setColor(textStartColor);
            mTextStartPaint.setAntiAlias(true);
        }
    }

绘制文字主要就是计算baseLine,这里我就推荐个博客
http://blog.csdn.net/harvic880925/article/details/50423762

//(fontMetrics.bottom+fontMetrics.top) = top线Y+bottom线Y-2*baseLine线Y
int baseLine = (int)((target.bottom + target.top - fontMetrics.bottom - fontMetrics.top) / 2.0f);

这里写图片描述
这里写图片描述

以下是计算的结论依据

    ascent = ascent线的y坐标 - baseline线的y坐标;//负数
    descent = descent线的y坐标 - baseline线的y坐标;//正数
    top = top线的y坐标 - baseline线的y坐标;//负数
    bottom = bottom线的y坐标 - baseline线的y坐标;//正数
    leading = top线的y坐标 - ascent线的y坐标;//负数
    ----------------------------------------
    ascent线Y坐标 = baseline线的y坐标 + fontMetric.ascent;
    descent线Y坐标 = baseline线的y坐标 + fontMetric.descent;
    top线Y坐标 = baseline线的y坐标 + fontMetric.top;
    bottom线Y坐标 = baseline线的y坐标 + fontMetric.bottom;

colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>

    <color name="textColor">#5A5A5A</color>
    <color name="divider">#CCCCCC</color>
    <color name="white">#ffffff</color>
    <color name="red">#FFff0000</color>
</resources>

dimens.xml

<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
    <dimen name="textSize1">20sp</dimen>
    <dimen name="textSize2">15sp</dimen>

    <dimen name="padding">20dp</dimen>
    <dimen name="margin">10dp</dimen>
    <dimen name="MenuitemHeight">60dp</dimen>
</resources>

styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="SetItemStyleBase">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">@dimen/MenuitemHeight</item>
        <item name="android:paddingLeft">@dimen/padding</item>
        <item name="android:paddingRight">@dimen/padding</item>
        <item name="com.safly:textStartColor">@color/textColor</item>
        <item name="com.safly:textStartSize">@dimen/textSize2</item>
    </style>
</resources>

devider.xml

<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="0.33dp"
    android:background="@color/divider"/>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值