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

来源:互联网 发布:nginx ip hash 编辑:程序博客网 时间:2024/06/05 14:57

一个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
原创粉丝点击