1. 程式人生 > >Android控制元件之LabelView

Android控制元件之LabelView

          最近研究android控制元件開發,學習無捷徑,逛github,看到一個LabelView的demo,感覺還是挺不錯,也比較實用,所以拿來學習了一下。

        附上源專案的地址:https://github.com/linger1216/labelview

        效果圖:

        

          再開始學習原始碼之前我們可以先分析build的過程:

                 label是放到原始標籤之上的,所以label可能是一個單獨的控制元件疊加到view之上。可惜像Button這樣的控制元件             是不支援含有子View的,所以排除了這種可能;那麼另一種可能就是在控制元件的指定位置繪製一個label了。

          

          下面進入原始碼:

           我們直接進入builder,

           在這之前我們還需要明確 onDraw onMeasure onLayout 三個函式

                  onDraw:所有繪製view 的動作在這裡

                  onMeasure:決定了view的大小

                  onLayout:決定了view 的位置

           首先先分析下對與label這個view需要設定什麼樣的屬性:

                   

                   position:位置(Left_top,Right_top,Left_bottom,Right_bottom)

                   visible:可見(true,false)

                   height:label寬度(個人喜好,)

                   distance:label距角的距離

                   textSize:字型大小

                   textColor:字型顏色

                   background:背景色

          以上這些屬性我們需要先設定好預設值,以便於我們通過TypedArray獲取設定的引數。

          

public static final int RIGHT_TOP = 1;
    public static final int LEFT_TOP = 2;
    public static final int RIGHT_BOTTOM = 3;
    public static final int LEFT_BOTTOM = 4;
    public static final boolean DEAFULT_VISIABLE = true;
    public static final int DEFAULT_RECT_HEIGHT = 10;
    public static final int DEFAULT_RECT_DISTANCE = 20;
    public static final int DEFAULT_TEXT_SIZE = 10;
    public static final int DEFAULT_TEXT_COLOR = 0xffffffff;
    public static final int DEFAULT_BACKGROUND_COLOR = 0x9f27CDC0;
    public static final int DEFAULT_GRAVITY = LEFT_TOP;

              從xml中的 屬性獲取值

 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.labelbuilder,defStyleAttr,0);
        builder.setLabelHeight(view,ta.getDimensionPixelSize(R.styleable.labelbuilder_labelHeight, builder.dip2Px(DEFAULT_RECT_HEIGHT)));
        builder.setLabelDistance(view,ta.getDimensionPixelSize(R.styleable.labelbuilder_labelDistance, builder.dip2Px(DEFAULT_RECT_DISTANCE)));
        builder.setTextSize(view,ta.getDimensionPixelSize(R.styleable.labelbuilder_labelTextSize, builder.dip2Px(DEFAULT_TEXT_SIZE)));
        builder.setText(view, ta.getString(R.styleable.labelbuilder_labelText));
        builder.setTextColor(view, ta.getColor(R.styleable.labelbuilder_labelTextColor, DEFAULT_TEXT_COLOR));
        builder.setBackgoundColor(view, ta.getColor(R.styleable.labelbuilder_labelBackground, DEFAULT_BACKGROUND_COLOR));
        builder.setOrientation(view, ta.getInt(R.styleable.labelbuilder_labelGravity, DEFAULT_GRAVITY));
        builder.setLabelVisiable(view, ta.getBoolean(R.styleable.labelbuilder_labelVisibility, DEAFULT_VISIABLE));
        builder.setAlpha(view, ta.getInteger(R.styleable.labelbuilder_labelAlpha,0));
        ta.recycle();
        最後一定記得recycle(),

        為什麼要呼叫recycle()方法呢,大概的解釋是TypeArray 採用的是 池-單例模式,如果不回收,每次View 的 create都要new 一個物件,然後等個GC(Gabage Collection),對記憶體是不小的開銷,具體可以參考這裡。

        http://blog.csdn.net/Monicabg/article/details/45014327


        到這裡,我們相關的屬性就設定好了,

        接下來就是繪製我們的View 了。

        由於我們這個只是一個Label的builder,我們要想讓View 應用我們的Builder,就需要提供我們必要的引數。

               Canvas:View的畫布,我們View 的繪製都是在View的這個Canvas上製作的。

               MeasuredHeight:

               MeasuredWidth:這兩個是控制元件的寬度和高度。我們需要這兩個引數來確定我們要畫的位置。

        引數傳進來後我們來確定下畫label的方案(以繪製右上角的Label為例):

               繪製的位置受到distance(上邊有提到,是角頂點距label的距離),和height(label寬度影響。

               繪製的過程就是繪製一個height寬度的Line,並延該方向繪製Label上的文字。

               在繪製前還要做的事就是提前初始化好Pain、Path

             

rectPaint = new Paint();
        rectPaint.setAntiAlias(true);
        rectPaint.setDither(true);
        rectPaint.setStyle(Paint.Style.STROKE);
        rectPaint.setStrokeJoin(Paint.Join.ROUND); //set the type one line join another one
        rectPaint.setStrokeCap(Paint.Cap.SQUARE);  //set the type of line's begin and end
        rectPath = new Path();
textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setDither(true);
        textPaint.setStrokeCap(Paint.Cap.SQUARE);
        textPaint.setStrokeJoin(Paint.Join.ROUND);


        textBound = new Rect();

               ①、確定Line的起始座標和終止座標。

                      

startX = measuredWidth - 1.4142f*(height/2+distance);
                startY = 0;
                endX = measuredWidth;
                endY = 1.4142*(height/2+distance);

               ②、繪製路徑

rectPaint.setStrokeWidth(height);//in onDraw()
        rectPath.reset();//remember to reset path
        rectPath.moveTo(startX, startY);
        rectPath.lineTo(endX, endY);
        canvas.drawPath(rectPath, rectPaint);

               ③、如果有文字,我們還要在上面繪製文字

</pre><pre name="code" class="java">if(text!=null&&!text.equals("")) {//in onDraw()
            textPaint.setTextSize(textSize);
            textPaint.setColor(textColor);
            textPaint.getTextBounds(text, 0, text.length(), textBound); //get TextBound

            float begin_h_offset = (distance+height/2) - textBound.width() / 2;
            if (begin_h_offset < 0) {  //如果text寬度大於label的長度 容納不下
                begin_h_offset = 0;
            }
            //在路徑上繪製文字 
            canvas.drawTextOnPath(text, rectPath, begin_h_offset, textBound.height() / 2, textPaint);
        }
        通過以上步驟基本上就完成了Label的繪製。

        但為了起到Builder的作用,使用於所有的View,我們可以在建構函式中這樣寫

public LabelBuilder(Context context,AttributeSet attrs,int defStyleAttr){//get attrs in builder
...
}
 public void onDraw(Canvas canvas,int measuredWidth,int measuredHeight){
...
}
        完整原始碼可以在github中檢視clone,也可以留言博主會及時發給你,共同學習。

        https://github.com/linger1216/labelview


      ps: 博主認為根據github中p出的distance的介紹,label的繪製座標計算感覺在原文中有錯誤,所以本文中的座標計算並非原文的座標計算,如果博主的錯誤,歡迎大家指正。