Android 进阶之路 自定义View(一)——初步尝试

来源:互联网 发布:哈佛法学院 知乎 编辑:程序博客网 时间:2024/04/28 17:19
粑粑怪参加工作半年有余,作为一个技术人员来讲到现在才开始动手写博客确实有些晚,遂决定将第一个自定义View作为我的第一篇博客参加工作的半年中在产品同事的“督促“下技术水平确实扎实不少,但自定义View的内容因为工期紧张未曾深入了解学习,近日工期不紧把握机会踏入自定义View这个神秘的领域,闲话少叙先说下这个自定义的倒计时器的设计思路。这个倒计时器是个简单的自定义View首先要自定义View的属性在res/values/ 下建立一个attrs.xml
<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="CountDownTextView">        <attr name="countdownText" format="string"/>        <attr name="countdownTextColor" format="color"/>        <attr name="countdownTextSize" format="dimension"/>    </declare-styleable></resources>

这里定义了文字内容,字体颜色和字体大小三个属性,fromat是属性值的类型,属性的类型共有:string,color,demension,integer,enum,reference,float,boolean,fraction,flag 几种。
自定义属性后就可以开始自定义我们自己的View了

    /*    * 文本*/    private String countdownText;    /*    * 文本颜色*/    private int countdownTextColor = Color.BLACK;    /*    * 文本大小*/    private int countdownTextSize;    /*控件所占空间大小*/    private Rect mBond;    private Paint mPaint;    public CountDownTextView(Context context) {        this(context,null);    }    public CountDownTextView(Context context, AttributeSet attrs) {        this(context,attrs,0);    }    public CountDownTextView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        CountDownThread thread = new CountDownThread();        thread.start();        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CountDownTextView        ,defStyleAttr,0);        int n = a.getIndexCount();        for (int i= 0;i<n;i++){            int attr = a.getIndex(i);            switch (attr){                case R.styleable.CountDownTextView_countdownText:                    countdownText = a.getString(attr);                    break;                case R.styleable.CountDownTextView_countdownTextColor:                    countdownTextColor = a.getColor(attr, Color.BLACK);                    break;                case R.styleable.CountDownTextView_countdownTextSize:                    countdownTextSize = a.getDimensionPixelSize(attr,                            (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,                                    16,getResources().getDisplayMetrics()));                    break;            }        }        a.recycle();        /*        * 设置画笔以及控件大小*/        mPaint = new Paint();        mPaint.setTextSize(countdownTextSize);        mPaint.setColor(countdownTextColor);        mBond = new Rect();        mPaint.getTextBounds(countdownText, 0, countdownText.length(), mBond);    }

解析获得的属性

a.getDimensionPixelSize(attr,                       (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,16,getResources().getDisplayMetrics()));

getDimensionPixelSize 从第一个参数中获取文字的大小,第二个参数是设置默认值。
接下来设置View本身的大小

@Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int width = 0;        int height = 0;        int specMode = MeasureSpec.getMode(widthMeasureSpec);        int specSize = MeasureSpec.getSize(widthMeasureSpec);        switch (specMode){            case MeasureSpec.EXACTLY:                width = getPaddingLeft()+getPaddingRight()+specSize;                break;            case MeasureSpec.AT_MOST:                int size = getPaddingLeft()+getPaddingRight()+mBond.width();                width = Math.min(size,specSize);                break;        }        specMode = MeasureSpec.getMode(heightMeasureSpec);        specSize = MeasureSpec.getSize(heightMeasureSpec);        switch (specMode){            case MeasureSpec.EXACTLY:                height = getPaddingTop()+getPaddingBottom()+specSize;                break;            case MeasureSpec.AT_MOST:                int size = getPaddingBottom()+getPaddingTop() + mBond.height();                height = Math.min(size,specSize);                break;        }        setMeasuredDimension(width, height);    }

在这里我们要获取两个值,一个是MeasureSpec的模式,另一个就是Size值了。MeasureSpec的模式有三种,分别是:
EXACTLY:一般是设置的明确的值或者是设了MATCH_PARENT
AT_MOST:一般是设置了WARP_CONTENT,系统传过来的是View所能占到的最大值。
UNSPECIFIED:表示子布局想要设多大就设多大,很少使用(目前还没碰到过)。
在上面的代码中我们判断MeasureSpec的模式,如果是EXACTLY就将View的大小设为MeasureSpec的Size值,如果是AT_MOST就判断View中内容所占空间与最大空间谁更小,选取更小的作为View的大小。
接下来可以将我们的自定义View画出来了,需要重写onDraw()。

@Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        mPaint.setColor(countdownTextColor);        canvas.drawText(countdownText, getWidth() / 2 - mBond.width() / 2,                getHeight() / 2 + mBond.height() / 2, mPaint);    }

看上去很简单不是吗?但是这里有个坑。那就是drawText方法啦。它的第一个参数就是我们要显示的文字的内容,第二的参数就是文字横向起始的位置,注意前方有坑,第三个参数不是文字纵向起始的位置也不是文字纵向结尾的位置更不是文字纵向中心的位置。看下图:
这里写图片描述
它的位置就是图中baseLine那条线的位置,想讲文字居中话网上有很多算法。粑粑怪就不再这废话了。
做完这些我们就可以在自定义的View中显示文字了,是不是很简单没有想象中的那么难?
下面就可以开始实现倒计时的功能了,首先要 new一个子线程将倒计时的功能放到子线程中做

class CountDownThread extends Thread{        @Override        public void run() {            while (true){                try {                    sleep(1000);                    second--;                    countDown(second)                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }private boolean countDown(int second){        Message message = new Message();        message.what = INVALIDATE;        if (second>3600){            int hr = second/3600;            int min = second%3600/60;//            Log.e("分钟",min+"");            int sec = second%3600%60;//            Log.e("秒",sec+"");            countdownText = hr +"小时"+min+"分钟"+sec+"秒";            mhanler.sendMessage(message);            return true;        }else if (second>60){            int min = second/60;            int sec = second%60;            countdownText = min + "分钟"+sec+"秒";            mhanler.sendMessage(message);            return true;        }else if (second>30){            countdownText = second+"秒";            mhanler.sendMessage(message);            return true;        }else if (second>0){            countdownText = second+"秒";            countdownTextColor = Color.RED;            mhanler.sendMessage(message);            return true;        }else            return false;    }

在构造方法中开启我们的子线程

CountDownThread thread = new CountDownThread();        thread.start();

然后写一个方法获取倒计时的总时间

public void setTime(int second){            this.second = second;    }

然后我们就可以调用自定义好的倒计时器了,在XML文件中调用我们自定义的View

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:countdown="http://schemas.android.com/com.zjk.zjkview"    android:layout_width="match_parent"    android:layout_height="match_parent"    xmlns:app="http://schemas.android.com/apk/res-auto">    <com.zjk.zjkview.myview.CountDownTextView        android:layout_width="match_parent"        android:layout_height="25dp"        app:countdownText=""        app:countdownTextSize="16dp"        android:id="@+id/countdown_textView"        android:layout_centerVertical="true"        android:layout_centerHorizontal="true" /></RelativeLayout>

这里我把它放到了一个ListView当中,上面的代码就是ListView的item的布局
我们在自定义的ListView的Adapter中进行进一步的调用

public class CountDownListAdapter extends BaseAdapter{    private Context context;    private List<Integer> list;    int i = 0;    public CountDownListAdapter (Context context,List<Integer> list){        this.list = list;        this.context = context;    }    @Override    public int getCount() {        return list.size();    }    @Override    public Object getItem(int position) {        return null;    }    @Override    public long getItemId(int position) {        return 0;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewHander viewHander;        if (convertView == null) {            LayoutInflater inflater = LayoutInflater.from(context);            convertView = inflater.inflate(R.layout.countdown_item,null);            viewHander = new ViewHander();            viewHander.textView = (CountDownTextView) convertView.                    findViewById(R.id.countdown_textView);            convertView.setTag(viewHander);        }else {            viewHander = (ViewHander) convertView.getTag();        }        ((ViewHander) convertView.getTag()).textView.setTime(list.get(position));        viewHander.textView.setCountDownID(position);        viewHander.textView.setOnCountDownListener(this);        return convertView;    }    class ViewHander{        CountDownTextView textView;    }}

这样一个倒计时器就基本写好了,看下效果图

这里写图片描述

到这里新的问题来了,倒计时结束了应该怎么办呢?那我们就来写一个监听事件吧。上代码

public interface CountDownListener{        /*        * 倒计时停止调用        * @param 设置过的该TextView的标示*/        void onCountDownStop(int id,CountDownTextView textView);    }private CountDownListener listener;//倒计时停止监听器public void setOnCountDownListener(            CountDownListener listener    ){        this.listener = listener;    }

我们在自定义View的类中写一个借口,然后在倒计时结束的时候调用接口中的方法

class CountDownThread extends Thread{        @Override        public void run() {            while (true){                try {                    sleep(1000);                    second--;                    if (!countDown(second)){                        listener.onCountDownStop(id,CountDownTextView.this);                        continue;                    }                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }

然后我们在自定义的Adapter中加入监听,下面贴上完整的Adapter代码

public class CountDownListAdapter extends BaseAdapter implements CountDownTextView.CountDownListener {    private Context context;    private List<Integer> list;    int i = 0;    public CountDownListAdapter (Context context,List<Integer> list){        this.list = list;        this.context = context;    }    @Override    public int getCount() {        return list.size();    }    @Override    public Object getItem(int position) {        return null;    }    @Override    public long getItemId(int position) {        return 0;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewHander viewHander;        if (convertView == null) {            LayoutInflater inflater = LayoutInflater.from(context);            convertView = inflater.inflate(R.layout.countdown_item,null);            viewHander = new ViewHander();            viewHander.textView = (CountDownTextView) convertView.                    findViewById(R.id.countdown_textView);            convertView.setTag(viewHander);        }else {            viewHander = (ViewHander) convertView.getTag();        }        ((ViewHander) convertView.getTag()).textView.setTime(list.get(position));        viewHander.textView.setCountDownID(position);        viewHander.textView.setOnCountDownListener(this);        return convertView;    }    @Override    public void onCountDownStop(int id, CountDownTextView textView) {        textView.setText("倒计时结束");    }    class ViewHander{        CountDownTextView textView;    }}

在自定义View中加入setText()方法

public void setText(String text){        countdownText = text;        postInvalidate();    }

这样我们的倒计时器就完工了,来看下最终的效果图
这里写图片描述

下面是源码地址,不要积分哦!http://download.csdn.net/detail/asdwkl2584561379/9481048

博客为本人原创,转载请注明地址http://blog.csdn.net/asdwkl2584561379/article/details/51025998

本人新手有问题请指正

1 0
原创粉丝点击