一个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"/>