4、快速实现自定义View

来源:互联网 发布:数据治理 权威定义 编辑:程序博客网 时间:2024/04/27 13:46

通常来说,自定义View有三种表现形式:自绘View,组合控件,继承控件。

一、自绘View

 自绘View,顾名思义,就是为了实现一个效果,但是Google没有提供现成的控件,这个时候,我们可以通过自绘View绘制出想要的View。

1、定义控件属性

我们需要在Values文件夹下新建attrs.xml文件,用来定义控件的属性。
     <?xml version="1.0" encoding="UTF-8"?>         <resources>            <declare-styleable name="RoundProgressBar">                     <attr name="roundColor" format="color"/>            <attr name="roundProgressColor" format="color"/>            <attr name="roundWidth" format="dimension"></attr>            <attr name="textColor" format="color" />             <attr name="textSize" format="dimension" />             <attr name="max" format="integer"></attr>             <attr name="textIsDisplayable" format="boolean"></attr>            <attr name="style">                <enum name="STROKE" value="0"></enum>                <enum name="FILL" value="1"></enum>            </attr>        </declare-styleable>      </resources>  
其中format有string,color,demension,integer,enum,reference,float,boolean,fraction,flag等类型。

2、在自绘View的构造方法中,获取自定义属性。

 我们先调用context.obtainStyledAttributes(attrs,R.styleable.RoundProgressBar)来获取TypedArray,然后从TypedArray获取我们定义的属性。
    roundColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundColor, Color.RED);    roundProgressColor = mTypedArray.getColor(R.styleable.RoundProgressBar_roundProgressColor, Color.GREEN);    textColor = mTypedArray.getColor(R.styleable.RoundProgressBar_textColor, Color.GREEN);    textSize = mTypedArray.getDimension(R.styleable.RoundProgressBar_textSize, 15);    roundWidth = mTypedArray.getDimension(R.styleable.RoundProgressBar_roundWidth, 5);    max = mTypedArray.getInteger(R.styleable.RoundProgressBar_max, 100);    textIsDisplayable = mTypedArray.getBoolean(R.styleable.RoundProgressBar_textIsDisplayable, true);    style = mTypedArray.getInt(R.styleable.RoundProgressBar_style, 0);

3、重写onDraw(),onMeasure()方法,其中onMeasure()不是必须要重写。

package com.example.roundprogressbar;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.RectF;import android.graphics.Typeface;import android.util.AttributeSet;import android.util.Log;import android.view.View;import com.example.circlepregress.R;/** * 仿iphone带进度的进度条,线程安全的View,可直接在线程中更新进度 *  */public class RoundProgressBar extends View {    /**     * 画笔对象的引用     */    private Paint paint;    /**     * 圆环的颜色     */    private int roundColor;    /**     * 圆环进度的颜色     */    private int roundProgressColor;    /**     * 中间进度百分比的字符串的颜色     */    private int textColor;    /**     * 中间进度百分比的字符串的字体     */    private float textSize;    /**     * 圆环的宽度     */    private float roundWidth;    /**     * 最大进度     */    private int max;    /**     * 当前进度     */    private int progress;    /**     * 是否显示中间的进度     */    private boolean textIsDisplayable;    /**     * 进度的风格,实心或者空心     */    private int style;    public static final int STROKE = 0;    public static final int FILL = 1;    public RoundProgressBar(Context context) {        this(context, null);    }    public RoundProgressBar(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        paint = new Paint();        TypedArray mTypedArray = context.obtainStyledAttributes(attrs,                R.styleable.RoundProgressBar);        // 获取自定义属性和默认值        roundColor = mTypedArray.getColor(                R.styleable.RoundProgressBar_roundColor, Color.RED);        roundProgressColor = mTypedArray.getColor(                R.styleable.RoundProgressBar_roundProgressColor, Color.GREEN);        textColor = mTypedArray.getColor(                R.styleable.RoundProgressBar_textColor, Color.GREEN);        textSize = mTypedArray.getDimension(                R.styleable.RoundProgressBar_textSize, 15);        roundWidth = mTypedArray.getDimension(                R.styleable.RoundProgressBar_roundWidth, 5);        max = mTypedArray.getInteger(R.styleable.RoundProgressBar_max, 100);        textIsDisplayable = mTypedArray.getBoolean(                R.styleable.RoundProgressBar_textIsDisplayable, true);        style = mTypedArray.getInt(R.styleable.RoundProgressBar_style, 0);        mTypedArray.recycle();    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        /**         * 画最外层的大圆环         */        int centre = getWidth() / 2; // 获取圆心的x坐标        int radius = (int) (centre - roundWidth / 2); // 圆环的半径        paint.setColor(roundColor); // 设置圆环的颜色        paint.setStyle(Paint.Style.STROKE); // 设置空心        paint.setStrokeWidth(roundWidth); // 设置圆环的宽度        paint.setAntiAlias(true); // 消除锯齿        canvas.drawCircle(centre, centre, radius, paint); // 画出圆环        Log.e("log", centre + "");        /**         * 画进度百分比         */        paint.setStrokeWidth(0);        paint.setColor(textColor);        paint.setTextSize(textSize);        paint.setTypeface(Typeface.DEFAULT_BOLD); // 设置字体        int percent = (int) (((float) progress / (float) max) * 100); // 中间的进度百分比,先转换成float在进行除法运算,不然都为0        float textWidth = paint.measureText(percent + "%"); // 测量字体宽度,我们需要根据字体的宽度设置在圆环中间        if (textIsDisplayable && percent != 0 && style == STROKE) {            canvas.drawText(percent + "%", centre - textWidth / 2, centre                    + textSize / 2, paint); // 画出进度百分比        }        /**         * 画圆弧 ,画圆环的进度         */        // 设置进度是实心还是空心        paint.setStrokeWidth(roundWidth); // 设置圆环的宽度        paint.setColor(roundProgressColor); // 设置进度的颜色        RectF oval = new RectF(centre - radius, centre - radius, centre                + radius, centre + radius); // 用于定义的圆弧的形状和大小的界限        switch (style) {        case STROKE: {            paint.setStyle(Paint.Style.STROKE);            canvas.drawArc(oval, 0, 360 * progress / max, false, paint); // 根据进度画圆弧            break;        }        case FILL: {            paint.setStyle(Paint.Style.FILL_AND_STROKE);            if (progress != 0)                canvas.drawArc(oval, 0, 360 * progress / max, true, paint); // 根据进度画圆弧            break;        }        }    }    public synchronized int getMax() {        return max;    }    /**     * 设置进度的最大值     *      * @param max     */    public synchronized void setMax(int max) {        if (max < 0) {            throw new IllegalArgumentException("max not less than 0");        }        this.max = max;    }    /**     * 获取进度.需要同步     *      * @return     */    public synchronized int getProgress() {        return progress;    }    /**     * 设置进度,此为线程安全控件,由于考虑多线的问题,需要同步 刷新界面调用postInvalidate()能在非UI线程刷新     *      * @param progress     */    public synchronized void setProgress(int progress) {        if (progress < 0) {            throw new IllegalArgumentException("progress not less than 0");        }        if (progress > max) {            progress = max;        }        if (progress <= max) {            this.progress = progress;            postInvalidate();        }    }    public int getCricleColor() {        return roundColor;    }    public void setCricleColor(int cricleColor) {        this.roundColor = cricleColor;    }    public int getCricleProgressColor() {        return roundProgressColor;    }    public void setCricleProgressColor(int cricleProgressColor) {        this.roundProgressColor = cricleProgressColor;    }    public int getTextColor() {        return textColor;    }    public void setTextColor(int textColor) {        this.textColor = textColor;    }    public float getTextSize() {        return textSize;    }    public void setTextSize(float textSize) {        this.textSize = textSize;    }    public float getRoundWidth() {        return roundWidth;    }    public void setRoundWidth(float roundWidth) {        this.roundWidth = roundWidth;    }}

4、布局文件中使用自定义组件。

在根布局上加上: xmlns:android_custom=”http://schemas.android.com/apk/res/com.example.circlepregress”
http://schemas.android.com/apk/res/:自定义属性的前缀。
com.example.circlepregress:斜体部分为包名。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:android_custom="http://schemas.android.com/apk/res/com.example.circlepregress"    xmlns:tools="http://schemas.android.com/tools"     android:layout_width="match_parent"     android:layout_height="match_parent" >     <com.example.roundprogressbar.RoundProgressBar          android:id="@+id/roundProgressBar2"         android:layout_width="80dip"         android:layout_height="80dip"         android:layout_alignLeft="@+id/roundProgressBar1"         android:layout_alignParentBottom="true"             android:layout_marginBottom="78dp"         android_custom:roundColor="#D1D1D1"         android_custom:roundProgressColor="@android:color/black"         android_custom:textColor="#9A32CD"         android_custom:textIsDisplayable="false"         android_custom:roundWidth="10dip"         android_custom:textSize="18sp"/> </RelativeLayout> 

二、组合控件。

组合控件的意思就是使用原生的控件组合在一起,组成一个全新的控件。例如我们经常使用的分组标签。

public class GroupLabelView extends LinearLayout {    /** 简洁模式,适用于分组等标签 */    public static final int CONCISE_MODE = 0;    /** 普通模式,适用于列表标签 */    public static final int NORMAL_MODE = 1;    private TextView mLeftTextView;    // private View mBottomLine;    private int style;    public GroupLabelView(Context context) {        super(context);        initialize();    }    public GroupLabelView(Context context, AttributeSet attrs) {        super(context, attrs);        initialize();    }    public GroupLabelView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        initialize();    }    private void initialize() {        setOrientation(HORIZONTAL);        // setMinimumHeight(getResources().getDimensionPixelSize(R.dimen.listview_letter_label_height));        setMinimumHeight(getResources().getDimensionPixelSize(                R.dimen.people_label_view_min_height));        int paddingRight = getResources().getDimensionPixelSize(                R.dimen.list_item_padding_right);        int paddingLeft = getResources().getDimensionPixelSize(                R.dimen.people_letter_group_label_padding_left);        setPadding(paddingLeft, 0, paddingRight, 0);        setBackgroundResource(R.color.people_label_background_color);        mLeftTextView = new TextView(getContext());        mLeftTextView.setTextAppearance(getContext(),                R.style.ContactConciseLabelTextStyle);        mLeftTextView.setSingleLine(true);        mLeftTextView.setGravity(Gravity.CENTER_VERTICAL);        mLeftTextView.setPadding(                getResources().getDimensionPixelSize(                        R.dimen.people_label_view_left_padding), 0, 0, 0);        LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(                LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);        addView(mLeftTextView, textParams);        // mBottomLine = new View(getContext());        // mBottomLine.setBackgroundColor(getResources().getColor(R.color.people_listview_label_line_color));        // LinearLayout.LayoutParams rightParams = new        // LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 1);        // rightParams.gravity = Gravity.BOTTOM;        // addView(mBottomLine, rightParams);    }    public void setLeftText(CharSequence text) {        mLeftTextView.setText(text);    }}

三、继承控件。

对于已有的控件,需要扩展其功能,可以使用继承控件,例如下拉刷新的ListView。

1、首先定义下拉刷新的头布局:

<?xml version="1.0" encoding="utf-8"?><!-- ListView的头部 --><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:background="#000000" >    <!-- 内容 -->    <RelativeLayout        android:id="@+id/head_contentLayout"        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:paddingLeft="30dp" >        <!-- 箭头图像、进度条 -->        <FrameLayout            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_alignParentLeft="true"            android:layout_centerVertical="true" >            <!-- 箭头 -->            <ImageView                android:id="@+id/lvHeaderArrowIv"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_gravity="center"                android:src="@drawable/arrow" />            <!-- 进度条 -->            <ProgressBar                android:id="@+id/lvHeaderProgressBar"                style="?android:attr/progressBarStyleSmall"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_gravity="center"                android:visibility="gone" />        </FrameLayout>        <!-- 提示、最近更新 -->        <LinearLayout            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerHorizontal="true"            android:gravity="center_horizontal"            android:orientation="vertical" >            <!-- 提示 -->            <TextView                android:id="@+id/lvHeaderTipsTv"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:text="下拉刷新"                android:textColor="@color/white"                android:textSize="20sp" />            <!-- 最近更新 -->            <TextView                android:id="@+id/lvHeaderLastUpdatedTv"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:text="上次更新"                android:textColor="@color/gold"                android:textSize="10sp" />        </LinearLayout>    </RelativeLayout></LinearLayout>

2、然后继承ListView,实现下拉刷新。

package com.example.listview;import java.util.Date;import android.content.Context;import android.util.AttributeSet;import android.view.LayoutInflater;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.view.animation.LinearInterpolator;import android.view.animation.RotateAnimation;import android.widget.AbsListView;import android.widget.AbsListView.OnScrollListener;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.ListView;import android.widget.ProgressBar;import android.widget.TextView;public class MyListView extends ListView implements OnScrollListener {    private final static int RELEASE_To_REFRESH = 0;// 下拉过程的状态值    private final static int PULL_To_REFRESH = 1; // 从下拉返回到不刷新的状态值    private final static int REFRESHING = 2;// 正在刷新的状态值    private final static int DONE = 3;    private final static int LOADING = 4;    // 实际的padding的距离与界面上偏移距离的比例    private final static int RATIO = 3;    private LayoutInflater inflater;    // ListView头部下拉刷新的布局    private LinearLayout headerView;    private TextView lvHeaderTipsTv;    private TextView lvHeaderLastUpdatedTv;    private ImageView lvHeaderArrowIv;    private ProgressBar lvHeaderProgressBar;    // 定义头部下拉刷新的布局的高度    private int headerContentHeight;    private RotateAnimation animation;    private RotateAnimation reverseAnimation;    private int startY;    private int state;    private boolean isBack;    // 用于保证startY的值在一个完整的touch事件中只被记录一次    private boolean isRecored;    private OnRefreshListener refreshListener;    private boolean isRefreshable;    public MyListView(Context context) {        super(context);        init(context);    }    public MyListView(Context context, AttributeSet attrs) {        super(context, attrs);        init(context);    }    private void init(Context context) {        setCacheColorHint(context.getResources().getColor(R.color.transparent));        inflater = LayoutInflater.from(context);        headerView = (LinearLayout) inflater.inflate(R.layout.lv_header, null);        lvHeaderTipsTv = (TextView) headerView                .findViewById(R.id.lvHeaderTipsTv);        lvHeaderLastUpdatedTv = (TextView) headerView                .findViewById(R.id.lvHeaderLastUpdatedTv);        lvHeaderArrowIv = (ImageView) headerView                .findViewById(R.id.lvHeaderArrowIv);        // 设置下拉刷新图标的最小高度和宽度        lvHeaderArrowIv.setMinimumWidth(70);        lvHeaderArrowIv.setMinimumHeight(50);        lvHeaderProgressBar = (ProgressBar) headerView                .findViewById(R.id.lvHeaderProgressBar);        measureView(headerView);        headerContentHeight = headerView.getMeasuredHeight();        // 设置内边距,正好距离顶部为一个负的整个布局的高度,正好把头部隐藏        headerView.setPadding(0, -1 * headerContentHeight, 0, 0);        // 重绘一下        headerView.invalidate();        // 将下拉刷新的布局加入ListView的顶部        addHeaderView(headerView, null, false);        // 设置滚动监听事件        setOnScrollListener(this);        // 设置旋转动画事件        animation = new RotateAnimation(0, -180,                RotateAnimation.RELATIVE_TO_SELF, 0.5f,                RotateAnimation.RELATIVE_TO_SELF, 0.5f);        animation.setInterpolator(new LinearInterpolator());        animation.setDuration(250);        animation.setFillAfter(true);        reverseAnimation = new RotateAnimation(-180, 0,                RotateAnimation.RELATIVE_TO_SELF, 0.5f,                RotateAnimation.RELATIVE_TO_SELF, 0.5f);        reverseAnimation.setInterpolator(new LinearInterpolator());        reverseAnimation.setDuration(200);        reverseAnimation.setFillAfter(true);        // 一开始的状态就是下拉刷新完的状态,所以为DONE        state = DONE;        // 是否正在刷新        isRefreshable = false;    }    @Override    public void onScrollStateChanged(AbsListView view, int scrollState) {    }    @Override    public void onScroll(AbsListView view, int firstVisibleItem,            int visibleItemCount, int totalItemCount) {        if (firstVisibleItem == 0) {            isRefreshable = true;        } else {            isRefreshable = false;        }    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        if (isRefreshable) {            switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                if (!isRecored) {                    isRecored = true;                    startY = (int) ev.getY();// 手指按下时记录当前位置                }                break;            case MotionEvent.ACTION_UP:                if (state != REFRESHING && state != LOADING) {                    if (state == PULL_To_REFRESH) {                        state = DONE;                        changeHeaderViewByState();                    }                    if (state == RELEASE_To_REFRESH) {                        state = REFRESHING;                        changeHeaderViewByState();                        onLvRefresh();                    }                }                isRecored = false;                isBack = false;                break;            case MotionEvent.ACTION_MOVE:                int tempY = (int) ev.getY();                if (!isRecored) {                    isRecored = true;                    startY = tempY;                }                if (state != REFRESHING && isRecored && state != LOADING) {                    // 保证在设置padding的过程中,当前的位置一直是在head,否则如果当列表超出屏幕的话,当在上推的时候,列表会同时进行滚动                    // 可以松手去刷新了                    if (state == RELEASE_To_REFRESH) {                        setSelection(0);                        // 往上推了,推到了屏幕足够掩盖head的程度,但是还没有推到全部掩盖的地步                        if (((tempY - startY) / RATIO < headerContentHeight)// 由松开刷新状态转变到下拉刷新状态                                && (tempY - startY) > 0) {                            state = PULL_To_REFRESH;                            changeHeaderViewByState();                        }                        // 一下子推到顶了                        else if (tempY - startY <= 0) {// 由松开刷新状态转变到done状态                            state = DONE;                            changeHeaderViewByState();                        }                    }                    // 还没有到达显示松开刷新的时候,DONE或者是PULL_To_REFRESH状态                    if (state == PULL_To_REFRESH) {                        setSelection(0);                        // 下拉到可以进入RELEASE_TO_REFRESH的状态                        if ((tempY - startY) / RATIO >= headerContentHeight) {// 由done或者下拉刷新状态转变到松开刷新                            state = RELEASE_To_REFRESH;                            isBack = true;                            changeHeaderViewByState();                        }                        // 上推到顶了                        else if (tempY - startY <= 0) {// 由DOne或者下拉刷新状态转变到done状态                            state = DONE;                            changeHeaderViewByState();                        }                    }                    // done状态下                    if (state == DONE) {                        if (tempY - startY > 0) {                            state = PULL_To_REFRESH;                            changeHeaderViewByState();                        }                    }                    // 更新headView的size                    if (state == PULL_To_REFRESH) {                        headerView.setPadding(0, -1 * headerContentHeight                                + (tempY - startY) / RATIO, 0, 0);                    }                    // 更新headView的paddingTop                    if (state == RELEASE_To_REFRESH) {                        headerView.setPadding(0, (tempY - startY) / RATIO                                - headerContentHeight, 0, 0);                    }                }                break;            default:                break;            }        }        return super.onTouchEvent(ev);    }}

3、当状态改变时候,调用该方法,以更新界面

private void changeHeaderViewByState() {    switch (state) {    case RELEASE_To_REFRESH:        lvHeaderArrowIv.setVisibility(View.VISIBLE);        lvHeaderProgressBar.setVisibility(View.GONE);        lvHeaderTipsTv.setVisibility(View.VISIBLE);        lvHeaderLastUpdatedTv.setVisibility(View.VISIBLE);        lvHeaderArrowIv.clearAnimation();// 清除动画        lvHeaderArrowIv.startAnimation(animation);// 开始动画效果        lvHeaderTipsTv.setText("松开刷新");        break;    case PULL_To_REFRESH:        lvHeaderProgressBar.setVisibility(View.GONE);        lvHeaderTipsTv.setVisibility(View.VISIBLE);        lvHeaderLastUpdatedTv.setVisibility(View.VISIBLE);        lvHeaderArrowIv.clearAnimation();        lvHeaderArrowIv.setVisibility(View.VISIBLE);        // 是由RELEASE_To_REFRESH状态转变来的        if (isBack) {            isBack = false;            lvHeaderArrowIv.clearAnimation();            lvHeaderArrowIv.startAnimation(reverseAnimation);            lvHeaderTipsTv.setText("下拉刷新");        } else {            lvHeaderTipsTv.setText("下拉刷新");        }        break;    case REFRESHING:        headerView.setPadding(0, 0, 0, 0);        lvHeaderProgressBar.setVisibility(View.VISIBLE);        lvHeaderArrowIv.clearAnimation();        lvHeaderArrowIv.setVisibility(View.GONE);        lvHeaderTipsTv.setText("正在刷新...");        lvHeaderLastUpdatedTv.setVisibility(View.VISIBLE);        break;    case DONE:        headerView.setPadding(0, -1 * headerContentHeight, 0, 0);        lvHeaderProgressBar.setVisibility(View.GONE);        lvHeaderArrowIv.clearAnimation();        lvHeaderArrowIv.setImageResource(R.drawable.arrow);        lvHeaderTipsTv.setText("下拉刷新");        lvHeaderLastUpdatedTv.setVisibility(View.VISIBLE);        break;    }}

最后引用下拉刷新的ListView。

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="fill_parent"    android:layout_height="fill_parent"    android:background="#000000"    android:orientation="vertical" >    <com.example.listview.MyListView        android:id="@+id/lv"        android:layout_width="fill_parent"        android:layout_height="fill_parent" /></LinearLayout>
0 0