关于自定义view的思考

对我来说,写自定义view是一个特麻烦但是写完之后特有成就感的过程。写完之后我总是喜欢拿给别人看,去炫耀(当然只是在自己熟悉和关系不错的人群里),尽管它们看起来会很简陋。希望这次写的东西不会让大家觉得太过于简陋,有错误的还是得需要各大神的点醒和赐教,有批评建议的也请随时赐教。

一般的自定义view有三种方式:

  1. 继承具体的view,如textView,button等等;
  2. 继承一个容器view,如linearlayout,relativeLayout等等,然后给该容器添加一些具体的控件来组合成一个新的view;
  3. 继承View。这个方式需要重写包括onDraw,onMeasure,onLayout,onTouchEvent等等方法来绘制一个新的view

对于这三种方式,我在这里说一下自己的看法:

首先,对于我来说,我很少使用第一种方式。由于继承的是一个具体的view,这个view的功能一般都是很强大了。我们能做的,希望去做的,大概是改变形状或者添加一些形状功能等等。当然由于我目前基本上没有做过相应的需求,我也无法去说这个方式到底该怎么弄。因为在我目前认知里,如果准备大改某个view,我会用第三种方式,如果需要添加一些模块或者功能我会使用第二种方式。

然后,对于第二种方式,简单得说,就是控件组合,把一些控件组合起来,形成一个特殊形状和功能的view。这个是三种方式中我觉得最方便的,只要做好各个控件的摆放,并提供一些参数设置的方法,或者干脆把这些控件实例提供给使用者,让使用者自己去操作这些实例。这个方法的缺点也是有的,这里说的特殊形状,是由有规则的控件布局而成,所能表达的形状也是有限的。

对于第三种方式,简单地说,就是自己画,利用canvas和paint等一点一线的自己来画。这种方式是最具创造力的,可以画出几乎所有的形状。(我这里这么说,是我感觉这种方式最贴近自定义这个词)。但同时他的缺点也是巨大的,计算绘制的点、线、框、文字等等位置的值,是这个方式最大的挑战之一。我经常就发现,在我程序的某段里,全都是成员变量在那里加减乘除,如果不给成员变量添加/* ……/这样的注释,过不了多久就不知道那段程序到底在干什么。

对于这三种方式,第一种我几乎不用,在时间不够充裕的时候我会用第二种方式,在时间充裕的时候和view非常特殊需要自己绘制的时候我会使用第三种方式。

接下来我把我写过的一个类似seekBar的自定义view拿出来和大家探讨一下。这是一个非常简单的自定义view,就是我们常见的视频播放控件下边显示视频进度的一个进度条,不同的是他的左右各有一个textView来显示时间。

第二种方式(组合)创建自定义view

我用第二种方式写的代码:

自定义view:

package test2;

import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;

public class MySeekbarView extends LinearLayout{

    private TextView mTvCurrentTime;
    private TextView mTvTotalTime;
    private SeekBar mSeekbar;

    public MySeekbarView(Context context, AttributeSet attrs) {
        super(context, attrs);
        LinearLayout ll = new LinearLayout(context);
        ll.setGravity(Gravity.CENTER_VERTICAL);
        ll.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        mTvCurrentTime = new TextView(context);
        mTvCurrentTime.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
        mTvTotalTime = new TextView(context);
        mTvCurrentTime.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
        mSeekbar = new SeekBar(context);
        mSeekbar.setLayoutParams(new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1));
        ll.addView(mTvCurrentTime);
        ll.addView(mSeekbar);
        ll.addView(mTvTotalTime);
        this.addView(ll);
    }

    public TextView getCurrentTimeTextView(){
        return mTvCurrentTime;
    }

    public TextView getTotalTimeTextView(){
        return mTvTotalTime;
    }

    public SeekBar getSeekbar(){
        return mSeekbar;
    }

}

Activity的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#ffff00">

     <test2.MySeekbarView
        android:id="@+id/MySeekbarView"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="#ff00ff"
        />
</LinearLayout>

Activity:

package test2;

import com.example.test2.R;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.widget.SeekBar;
import android.widget.TextView;

public class Test2_seekbar extends Activity{

    private MySeekbarView view;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test2_seekbar_layout);
        view = (MySeekbarView)findViewById(R.id.MySeekbarView);
        TextView tv1 = view.getCurrentTimeTextView();
        tv1.setText("01:00");
        TextView tv2 = view.getTotalTimeTextView();
        tv2.setText("09:00");
        SeekBar seekbar = view.getSeekbar();
        seekbar.setThumb(ContextCompat.getDrawable(this, R.drawable.video_pb_indicator));
        seekbar.setProgress(50);

    }
}

结果:

有没有觉得很方便?在view的数量不多的情况下,直接新建view然后用addView()很方便快捷,但在view的数量有点大或者觉得脑子里形象不是很直观的情况下,我们一般会写一个布局然后通过LayoutInflater的

inflate方法来直接获得自己想要的这个已经布局好的view。

自定义view布局的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:gravity="center_vertical">
    <TextView
        android:id="@+id/tv1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="00:00"
        android:textSize="16sp"
        />
    <SeekBar
        android:id="@+id/seekbar"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:thumb="@drawable/video_pb_indicator"
        />
    <TextView
        android:id="@+id/tv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="55:55"
        android:textSize="16sp"
        />

</LinearLayout>

修改之后的自定义view:

package test2;

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;

import com.example.test2.R;

public class MySeekbarView extends LinearLayout{

    private TextView mTvCurrentTime;
    private TextView mTvTotalTime;
    private SeekBar mSeekbar;
    private View view;

    public MySeekbarView(Context context, AttributeSet attrs) {
        super(context, attrs);

        view = LayoutInflater.from(context).inflate(R.layout.myseekbar_view_layout, this);
        mTvCurrentTime = (TextView)view.findViewById(R.id.tv1);
        mTvTotalTime = (TextView)view.findViewById(R.id.tv2);
        mSeekbar = (SeekBar)view.findViewById(R.id.seekbar);
    }

    public TextView getCurrentTimeTextView(){
        return mTvCurrentTime;
    }

    public TextView getTotalTimeTextView(){
        return mTvTotalTime;
    }

    public SeekBar getSeekbar(){
        return mSeekbar;
    }

}

结果是一样。

有没有发现,哪里不一样。哈哈,发没发现我都要讲了。这是一个关于LayoutInflater的故事。我是在这个地方获得了很厚重的知识:Android LayoutInflater深度解析 给你带来全新的认识

这里边博主告诉我们LayoutInflater的Inflate的三个重构方法的差别:

Inflate(resId , null ):返回temp,没有执行setLayoutParams和addView

Inflate(resId , root,false ):返回temp,执行setLayoutParams,没有执行addView

Inflate(resId , root, true ):返回root,执行addView(view,params)

(呃在我那个程序中看到Inflate(resId , root)后没有addView()方法也可以正常显示,说明第三个参数默认为true。)

这样看来在使用Inflate方法的时候有三种选择:

1.

temp = LayoutInflater.from(context).Inflate(resId , null );

temp.setLayoutParams(…);

this.addView(temp);

2.

temp = LayoutInflater.from(context).Inflate(resId , root,false );

this.addView(temp);

3.

LayoutInflater.from(context).Inflate(resId , root,true );

可想而知他们之间的差别:

3是最简单的情况,添加单个temp,无特殊params要求;

2其次,可以添加多个temp,无特殊params要求;

1最麻烦,既可以添加多个view又可以添加特殊params要求,唯一不爽的是,需要给每个temp都添加一遍;

这里我还得提醒一下各位兄弟姐妹们(我最近在自定义的时候犯的错误,好不容易才找出来,希望各位兄姐妹们看了以后不要笑,也希望你们不会遇到这样的问题):

1.一定要尽量把那几个构造方法的重构都写上。其实以我目前的经验,对我有用的重构也就只有public MySeekbarView(Context context)和public MySeekbarView(Context context, AttributeSet attrs)。第一个我觉得主要是在随时随地通过context新建view的时候调用,第二个我觉得主要是在利用layout布局来创建view的时候调用。inflate方法中设置的layoutParams是从attrs中得到的,构造函数中没有传入AttributeSet attrs或者传入的为空,inflate中设置的layoutParams为null,然后你就会看见一片空白;

2.尽量利用布局创建view;

3.不通过布局创建view的时候一定不要忘记setLayoutParams,通过布局创建view但没有设置layoutParams时也要记得添加layoutParams;

4.这些对于linearlayout和relativelayout是有效的,对于viewGroup,呃,我并不是很确定,以后细细研究过后再来探讨,不过我认为linearlayout和relativelayout已经包含了很多内容;

第三种方式(draw)创建自定义view

是不是有些乱了?我只能说乱的还在后边。接下来我们来交流一下用第三种方法来自定义view。

package mydemo.myseekbardemo;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.OnGestureListener;
import android.view.MotionEvent;
import android.view.View;

public class MyView extends View{

    private Context mContext;
    /**
     * view的宽
     */
    private int mViewWidth;
    /**
     * view的高
     */
    private int mViewHeight;
    /**
     * 底部进度条的宽
     */
    private int mBottomBarWidth;
    /**
     * 进度条的高
     */
    private int mBarHeight = 6;
    /**
     * 底部进度条左边位置
     */
    private int mBottomBarLeft;
    /**
     * 底部进度条上边位置
     */
    private int mBottomBarTop;
    /**
     * 底部进度条右边位置
     */
    private int mBottomBarRight;
    /**
     * 底部进度条底边位置
     */
    private int mBottomBarBottom;

    /**
     * 顶部进度条的右边位置
     */
    private int mTopBarRight;
    /**
     * 文字的宽
     */
    private int mTvWidth;
    /**
     * 文字的高
     */
    private int mTvHeight;

    /**
     * 图片对象
     */
    private Bitmap mBitmapOfCirlcle = null;

    /**
     * 图片可见度,在0~255之间
     */
    private int mBitmapAlpha = 255;
    /**
     * 规定图片的宽
     */
    private int mBitmapWidth = 30;
    /**
     * 规定图片的高
     */
    private int mBitmapHeight = 30;
    /**
     * 画笔对象
     */
    private Paint mPaint;

    /**
     * 判断是否使用用户的图片
     */
    private boolean hasCirclePic = false;

    /**
     * 圆圈圆心的x坐标
     */
    private int mCircleX;
    /**
     * 圆圈圆心的y坐标
     */
    private int mCircleY;

    /**
     * 文字大小
     */
    private float mTextSize = 35;
    /**
     * 一些间距
     */
    private int mPadding = 10;
    /**
     * 圆圈的半径
     */
    private int mRadius = 16;
    /**
     * 顶部进度条的右边位置同时也是圆圈的x坐标
     */
    private int mPosition;
    /**
     * 顶部进度条初始百分比进度
     */
    private float mOriginalPercent = 0f;
    /**
     * 文本的颜色
     */
    private int mTextColor = Color.WHITE;
    /**
     * 底部进度条的颜色
     */
    private int mBottomBarColor = Color.GRAY;
    /**
     * 顶部进度条的颜色
     */
    private int mTopBarColor = Color.BLUE;
    /**
     * 圆圈的颜色
     */
    private int mCircleColor = Color.WHITE;

    /**
     * 用户点击位置状态
     */
    private int mWhereClickedState;
    /**
     * 用户点击在圆圈上
     */
    private final int WHERR_CLICKED_CIRCLE = 0;
    /**
     * 用户点击在进度条上
     */
    private final int WHERR_CLICKED_BAR = 1;
    /**
     * 用户点击在其他部分
     */
    private final int WHERR_CLICKED_VIEW = 2;

    /**
     * 实时时间文本
     */
    private String mCurrentTimeString = "00:00";
    /**
     * 总时间文本
     */
    private String mTotalTimeString = "00:00";

    /**
     * 手势管理对象
     */
    private GestureDetector mGestureDetector;

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);

        Log.i("tag", "MyView");

        mContext = context;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        mGestureDetector  = new GestureDetector(mContext, new OnGestureListener() {

            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                if(mWhereClickedState == WHERR_CLICKED_CIRCLE){
                }else if(mWhereClickedState == WHERR_CLICKED_BAR){
                    mPosition = (int) Math.round(e.getX()+0.5);
                    if(mPosition >= mBottomBarRight){
                        mPosition = mBottomBarRight;
                    }else if(mPosition <= mBottomBarLeft){
                        mPosition = mBottomBarLeft;
                    }
                    MyView.this.invalidate();
                }
                return false;
            }

            @Override
            public void onShowPress(MotionEvent e) {
                // TODO Auto-generated method stub

            }

            @Override
            public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
                    float distanceY) {
                if(mWhereClickedState == WHERR_CLICKED_CIRCLE){
                    mPosition = (int) Math.round(e2.getX()+0.5);
                    if(mPosition >= mBottomBarRight){
                        mPosition = mBottomBarRight;
                    }else if(mPosition <= mBottomBarLeft){
                        mPosition = mBottomBarLeft;
                    }
                    MyView.this.invalidate();
                }else if(mWhereClickedState == WHERR_CLICKED_BAR){
                }
                return false;
            }

            @Override
            public void onLongPress(MotionEvent e) {
                // TODO Auto-generated method stub

            }

            @Override
            public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                    float velocityY) {
                // TODO Auto-generated method stub
                return false;
            }

            @Override
            public boolean onDown(MotionEvent e) {
                float event_x = e.getX();
                float event_y = e.getY();
                mWhereClickedState = judgeWhereClicked(event_x, event_y);
                if(mWhereClickedState == WHERR_CLICKED_VIEW){
                    return false;
                }else{
                    return true;
                }
            }
        });
    }

    /**
     * 获得文本的宽高
     */
    private void initTvWidthAndHeight() {
        Rect rect = new Rect();
        mPaint.getTextBounds("00:00", 0, 5, rect);
        mTvWidth = rect.width();
        mTvHeight = rect.height();
    }

    @Override
    protected void onDraw(Canvas canvas) {

        Log.i("tag", "onDraw");

        mTopBarRight = mPosition;
        mCircleX = mTopBarRight;

        mPaint.setColor(mTextColor);
        canvas.drawText(mCurrentTimeString, mPadding, mBottomBarBottom+Math.round(mTvHeight/4.0+0.5), mPaint);
        canvas.drawText(mTotalTimeString, mViewWidth - mTvWidth - mPadding, mBottomBarBottom+Math.round(mTvHeight/4.0+0.5), mPaint);

        mPaint.setColor(mBottomBarColor);
        canvas.drawRect(mBottomBarLeft, mBottomBarTop, mBottomBarRight, mBottomBarBottom, mPaint);

        mPaint.setColor(mTopBarColor);
        canvas.drawRect(mBottomBarLeft, mBottomBarTop, mTopBarRight, mBottomBarBottom, mPaint);

        if(mBitmapOfCirlcle != null){
            mPaint.setAlpha(mBitmapAlpha);
            canvas.drawBitmap(mBitmapOfCirlcle, mCircleX-mBitmapWidth/2, mCircleY - mBitmapHeight/2, mPaint);
        }else{
            mPaint.setColor(mCircleColor);
            canvas.drawCircle(mCircleX, mCircleY, mRadius, mPaint);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        mPaint.setTextSize(mTextSize);
        //获得文本的宽高
        initTvWidthAndHeight();

        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        if(widthMode == MeasureSpec.AT_MOST){
            mViewWidth = mTvWidth*4;
        }else if(widthMode == MeasureSpec.EXACTLY){
            if(mTvWidth*4 >= widthSize){
                mViewWidth = mTvWidth*4;
            }else{
                mViewWidth = widthSize;
            }
        }

        widthMeasureSpec = MeasureSpec.makeMeasureSpec(mViewWidth, MeasureSpec.EXACTLY);

        if(heightMode == MeasureSpec.AT_MOST){
            mViewHeight = mTvHeight + 4*mPadding;
        }else if(heightMode == MeasureSpec.EXACTLY){
            if(heightSize <= mTvHeight+4*mPadding ){
                mViewHeight = mTvHeight+4*mPadding;
            }else{
                mViewHeight = heightSize;
            }
        }

        heightMeasureSpec = MeasureSpec.makeMeasureSpec(mViewHeight, MeasureSpec.EXACTLY);

        mBottomBarWidth = mViewWidth - 2*mTvWidth - 6*mPadding;

        mBottomBarLeft = mTvWidth + 3*mPadding;
        mBottomBarRight = mViewWidth - mTvWidth - 3*mPadding;
        mBottomBarBottom = (mViewHeight - mTvHeight)/2+mTvHeight;
        mBottomBarTop = mBottomBarBottom-mBarHeight;

        mCircleY = mBottomBarBottom - mBarHeight/2;

        mPosition = (int) Math.round(mBottomBarWidth * mOriginalPercent+0.5)+mBottomBarLeft;
        if(mPosition <= mBottomBarLeft){
            mPosition = mBottomBarLeft;
        }else if(mPosition >= mBottomBarRight){
            mPosition = mBottomBarRight;
        }

        if(mBitmapOfCirlcle != null){
            mBitmapOfCirlcle = Bitmap.createScaledBitmap(mBitmapOfCirlcle, mBitmapWidth, mBitmapHeight, false);
        }

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    /**
     * 设置总时间,默认格式"--:--"
     * @param strOfTime
     */
    public void setTotalTimeString(String strOfTime){

        Log.i("tag", "setTotalTimeString");

        mTotalTimeString = strOfTime;
    }

    /**
     * 设置实时时间,默认格式"--:--"
     * @param strOfTime
     */
    public void setCurrentTimeString(String strOfTime){

        Log.i("tag", "setCurrentTimeString");

        mCurrentTimeString = strOfTime;
    }

    /**
     * 设置图片的透明度
     * @param alpha 0~255
     */
    public void setBitmapAlpha(int alpha){

        Log.i("tag", "setBitmapAlpha");

        if(alpha < 0){
            alpha = 0;
        }else if(alpha > 255){
            alpha = 255;
        }
        mBitmapAlpha = alpha;
    }
    /**
     * 获得现在所在位置的百分比进度值
     * @return float
     */
    public float getPercent(){
        float percent = (mBottomBarRight - mPosition)*1.0f/mBottomBarWidth;
        if(percent < 0.0){
            percent = 0f;
        }else if(percent > 1){
            percent = 1f;
        }
        return percent;
    }
    /**
     * 获得当前进度值
     * @return int
     */
    public int getProgress(){

        float percent = (mBottomBarRight - mPosition)*1.0f/mBottomBarWidth;
        if(percent < 0.0){
            percent = 0f;
        }else if(percent > 1){
            percent = 1f;
        }

        int progress = (int) Math.round(percent*100+0.5);
        if(progress < 0){
            progress = 0;
        }else if(progress >100){
            progress = 100;
        }

        return progress;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(mGestureDetector != null){
            return  mGestureDetector.onTouchEvent(event);
        }else{
            return super.onTouchEvent(event);
        }
    }
    /**
     * 判断用户点击位置状态
     * @param x 用户点击的x坐标
     * @param y 用户点击的y坐标
     * @return 返回用户点击未知状态:
     *          WHERR_CLICKED_CIRCLE
     *          WHERR_CLICKED_BAR
     *          WHERR_CLICKED_VIEW
     */
    public int judgeWhereClicked(float x, float y){
        //s_x,s_y用来界定圆圈或者代替圆圈的图片的区域,s_x表示x坐标方向,圆心到区域边界的距离
        int s_x = 0;
        int s_y = 0;
        if(hasCirclePic){
            s_x = mBitmapWidth/2;
            s_y = mBitmapHeight/2;
        }else{
            s_x = mRadius;
            s_y = mRadius;
        }

        if( (x>=(mCircleX-s_x) && x<=(mCircleX + s_x)) && (y>=(mCircleY-s_y) && y<=(mCircleY + s_y)) ){
            //s_x和s_y界定的区域,即圆圈区域
            return WHERR_CLICKED_CIRCLE;
        }else if((x>=mBottomBarLeft && x<=mBottomBarRight) || (y <= mBottomBarBottom+5 && y >= mBottomBarTop+5 )){
            //进度条上除了圆圈区域的其他可响应点击部分
            return WHERR_CLICKED_BAR;
        }else{
            //view除了上述两部分的部分
            return WHERR_CLICKED_VIEW;
        }
    }

    /**
     * 设置初始百分比进度,0.0~1.0f
     * @param percent
     */
    public void setOriginalPercent(float percent){
        mOriginalPercent = percent;
        if(mOriginalPercent<0){
            mOriginalPercent = 0.0f;
        }else if(mOriginalPercent > 1.0f){
            mOriginalPercent = 1.0f;
        }
    }
    /**
     * 设置初始进度值,默认进度最大值为100
     * @param progress
     */
    public void setOriginalProgress(int progress){

        mOriginalPercent = progress/100f;
        if(mOriginalPercent<0){
            mOriginalPercent = 0.0f;
        }else if(mOriginalPercent > 1.0f){
            mOriginalPercent = 1.0f;
        }
    }

    /**
     * 通过传入进度百分比来更新进度条
     * @param percent
     */
    public void updatePositionFromPercent(float percent){

        Log.i("tag", "updatePositionFromPercent");
        if(percent<0){
            percent = 0.0f;
        }else if(percent > 1.0f){
            percent = 1.0f;
        }

        mPosition = (int) Math.round(mBottomBarWidth * percent+0.5)+mBottomBarLeft;
        if(mPosition <= mBottomBarLeft){
            mPosition = mBottomBarLeft;
        }else if(mPosition >= mBottomBarRight){
            mPosition = mBottomBarRight;
        }

        this.invalidate();
    }

    /**
     * 通过传入进度值来更新进度条
     * @param percent
     */
    public void updatePositionFromProgress(int progress){

        float percent = progress/100f;
        if(percent<0){
            percent = 0.0f;
        }else if(percent > 1.0f){
            percent = 1.0f;
        }

        mPosition = (int) Math.round(mBottomBarWidth * percent+0.5)+mBottomBarLeft;
        if(mPosition <= mBottomBarLeft){
            mPosition = mBottomBarLeft;
        }else if(mPosition >= mBottomBarRight){
            mPosition = mBottomBarRight;
        }

        this.invalidate();
    }

    /**
     * 设置文字颜色
     * @param color
     */
    public void setTextColor(int color){
        mTextColor = color;
    }
    /**
     * 设置文字大小
     * @param size
     */
    public void setTextSize(int size){
        mTextSize = size;
    }

    /**
     * 设置底部进度条颜色
     * @param color
     */
    public void setBottomBarColor(int color){
        mBottomBarColor = color;
    }

    /**
     * 设置顶部进度条颜色
     * @param color
     */
    public void setTopBarColor(int color){
        mTopBarColor = color;
    }

    /**
     * 获得实时时间文本
     * @return
     */
    public String getCurrentTimeString(){
        return mCurrentTimeString;
    }

    /**
     * 获得总时间文本
     * @return
     */
    public String getTotalTimeString(){
        return mTotalTimeString;
    }
    /**
     * 用其他图片更换进度条上的圆圈
     * @param bitmap
     */
    public void setThumbBitmap(Bitmap bitmap){
        mBitmapOfCirlcle = bitmap;
    }
}

哈哈,是不是感觉有点头大。其实很简单,一共两个部分,一是计算位置,二是绘制图形。其中最看重的就是位置的计算,位置计算好了,绘制就是分分钟的事了。canvas的绘制功能很强大,一切形状功能都可以轻松绘制出来。看着图形从一个点一条线到一个复杂图形甚至是拥有动画属性,这个过程是很有成就感的,也是我最喜欢的过程。

对canvas和paint了解还不深的我就不在这里露短了,只想告诉大家一些我知道的需要注意的问题:

1.现在的手机基本上都是支持硬件加速的,而并不是所有的canvas的操作都支持硬件加速的,比如canvas.drawPicture()等,所以在使用自定义view特别是自己绘制的自定义view的时候需要注意是否应该开启硬件加速;

2.最好不要频繁得创建paint对象,频繁创建对象本就是编程的大忌,绘制的过程更是如此;

3.想要使得view拥有动画属性,只要在静止的位置上添加偏移量,改变偏移量,就能完成动画的绘制;

4.尽量给成员变量添加/** ……/这样的注释,特别是在计算位置特别复杂的自定义view里,这样会更加便于阅读和回忆;

第一次写博客,没想到会花这么长的时间。以前觉得博客内容很少,写的应该蛮快的。今天我真真切切的感受到,写一篇博客真是相当不容易的。也许只是像我这种小新才会有这种感觉把。不过还蛮有成就感的,尽管我觉得真的没有写些什么。最后还是希望有什么批评建议的请随时赐教!

时间: 2024-10-12 14:25:20

关于自定义view的思考的相关文章

wing带你玩转自定义view系列(1) 仿360内存清理效果

本篇是接自 手把手带你做自定义view系列 宗旨都是一样,带大家一起来研究自定义view的实现,与其不同的是本系列省去了简单的坐标之类的讲解,重点在实现思路,用简洁明了的文章,来与大家一同一步步学习. 转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50500479 上一篇介绍了:神奇的贝塞尔曲线,这篇就来研究其应用. 我自己的学习方法是:学习了贝塞尔曲线之后,去研究他的规律,然后开始联想有没有见过类似的效果,最后自己去研究实现,在没

Android开发之自定义View专题(三):自定义GridView

gridview作为android开发中常用的组件,其功能十分强大.但是,我们有时候有很多特殊的需求,需要在其基础上进行改造.有时候会有移动gridView中item位置的需求,这个网上已经有很多例子,博主就不在描述.今天博主讲的是移动gridView中item中的内容.博主没看过网上那些移动item位置的demo,不知道其原理是不是和博主想的一样.博主思考过,似乎博主的这种实现原理似乎也可以用作实现移动item位置.而之前博主百思不得其解的小米手机的桌面的自定义乱序排放,似乎也可以用这个原理去

自定义View系列教程07--详解ViewGroup分发Touch事件

自定义View系列教程01–常用工具介绍 自定义View系列教程02–onMeasure源码详尽分析 自定义View系列教程03–onLayout源码详尽分析 自定义View系列教程04–Draw源码分析及其实践 自定义View系列教程05–示例分析 自定义View系列教程06–详解View的Touch事件处理 自定义View系列教程07–详解ViewGroup分发Touch事件 PS:如果觉得文章太长,那就直接看视频吧 在上一篇中已经分析完了View对于Touch事件的处理,在此基础上分析和理

android 自定义view 前的基础知识

本篇文章是自己自学自定义view前的准备,具体参考资料来自 Android LayoutInflater原理分析,带你一步步深入了解View(一) Android视图绘制流程完全解析,带你一步步深入了解View(二) Android视图状态及重绘流程分析,带你一步步深入了解View(三) Android自定义View的实现方法,带你一步步深入了解View(四) 这位大哥的系列博文,相当于自己看这些的一个思考吧. 一.首先学layoutInflater. 相信接触Android久一点的朋友对于La

Android自定义View(三、深入解析控件测量onMeasure)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51490283 本文出自:[openXu的博客] 目录: onMeasure什么时候会被调用 onMeasure方法执行流程 MeasureSpec类 从ViewGroup的onMeasure到View的onMeasure ViewGroup中三个测量子控件的方法 getChildMeasureSpec方法 View的onMeasure setMeasuredDimension ??在上一篇

自定义View 篇三 《手动打造ViewPage》

有了之前自定义View的理论基础,有了ViewPage.事件分发机制.滑动冲突.Scroller使用等相关知识的铺垫,今天纯手动打造一款ViewPage. 1.完成基本的显示: 在MainActivity中: public class MainActivity extends AppCompatActivity { private MyViewPage mViewPage; int[] imageIds = new int[]{ R.drawable.pic_0, R.drawable.pic_

九点(九宫格)式手势解锁自定义view

周末闲着没事,写了个手势解锁的view,实现起来也蛮快的,半天多一点时间就完事.把源码和资源贴出来,给大家分享,希望对大家有用. 效果,就跟手机上的九点手势解锁一样,上个图吧: 过程嘛感觉确实没啥好讲的了,涉及的知识以前的博客都说过了,无非就是canva,paint,touch事件这些,画画圆圈画画线条,剩下的就是细节处理逻辑了.都在代码里,所以这里就主要是贴资源吧. 这个自定义view就一个类,源码如下: package com.cc.library.view; import android.

iOS开发——笔记篇&amp;关于字典plist读取/字典转模型/自定义View/MVC/Xib的使用/MJExtension使用总结

关于字典plist读取/字典转模型/自定义View/MVC/Xib的使用/MJExtension使用总结 一:Plist读取 1 /******************************************************************************/ 2 一:简单plist读取 3 4 1:定义一个数组用来保存读取出来的plist数据 5 @property (nonatomic, strong) NSArray *shops; 6 7 2:使用懒加载的方

自定义View之实现日出日落太阳动效

以前也很羡慕网上大神随手写写就是一个很漂亮的自定义控件,所以我下决心也要学着去写,刚好最近复习了Android View的绘制流程知识,看来看去就是那些个知识点,没点产出总感觉很迷.现在个人呢用的是华为荣耀8手机,碰巧在看自带的天气APP时,滑到最下面看到那个动效图:日出时间和日落时间上边是一个半圆,白天任意的时刻(在日出和日落时间之间)都有对应一个太阳从日出时刻沿着半圆弧做动画特效,个人第一感觉就是:就拿这个来练练手啦!于是拿着笔和纸,画了模型图,甚至求什么sin.cos函数,有点过分了哈,还