1. 程式人生 > >安卓開發:仿微博自定義帶進度條和vip標識功能的圓形頭像IdentityImageView

安卓開發:仿微博自定義帶進度條和vip標識功能的圓形頭像IdentityImageView

*本篇文章已授權微信公眾號 guolin_blog(郭霖)獨家釋出

最近產品增加了兩個小功能,一個是頭像加一個進度條,用於升級提示,一個是身份標識功能,也就是標識Vip的功能,如圖:


這裡寫圖片描述

很多朋友看見這個小功能,肯定覺得特簡單,就是兩張圖片疊在一起嘛,用個RelaiveLayout或者其他佈局一下就搞定了 , 沒錯 , 是很簡單,但是如果需要動態設定這個頭像的大小,而且很多地方用到的話,在每個地方都去羅列的話,難免不開心並且出現大小錯位等問題,找了好久沒找到開源此控制元件的,只能自己動手了並分享給大家,有任何問題可加QQ群詢問:661614986,效果圖如下:


這裡寫圖片描述

具體控制元件特性為:

  • 有進度條,進度條顏色、寬度可隨意設定
  • 有標識身份,標識位置可隨意改變、可隱藏
  • 標識身份的可以是圖片,也可以隱藏圖片,設定文字

下面來具體說一下實現思路:

一、圓形圖片的實現:

首先要解決的就是要把圖片裁剪成圓形,這種控制元件很多,谷歌v4包下有個自帶的
CircleImageView,不過沒用過,用的是hdodenhof的CircleImageView ,原始碼也非常簡單易上手

其實這種圖片可以這樣理解,就是一個正方形裡面有個大圓,還有個小圓,小圓和大圓的和為長方形邊長,如圖:


這裡寫圖片描述
那麼這個時候就可以自定義一個View繼承ViewGroup,命名為:IdentityImageView;由於小圓和大圓半徑是有關係的,那麼重寫onMeasure方法可為:
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int viewWidthMode = MeasureSpec.getMode(widthMeasureSpec);
        int viewHeightMode = MeasureSpec.getMode(heightMeasureSpec);
        int
viewWidht = MeasureSpec.getSize(widthMeasureSpec); int viewHeight = MeasureSpec.getSize(heightMeasureSpec); switch (viewWidthMode) { case MeasureSpec.EXACTLY: //說明在佈局檔案中使用的是具體值:100dp或者match_parent //為了方便,讓半徑等於寬高中小的那個,再設定寬高至半徑大小 totalwidth = viewWidht < viewHeight ? viewWidht : viewHeight; float scale = 1 + radiusScale; int radius2 = totalwidth / 2; radius = (int) (radius2 / scale); break; case MeasureSpec.AT_MOST: //說明在佈局檔案中使用的是wrap_content: //這時我們也寫死寬高 radius = 200; totalwidth = (int) ((radius + radius * radiusScale) * 2); break; default: radius = 200; totalwidth = (int) ((radius + radius * radiusScale) * 2); break; } setMeasuredDimension(totalwidth, totalwidth); adjustThreeView(); }

神之憤怒


這裡寫圖片描述

二、初始化IdentityImageView:

public class IdentityImageView extends ViewGroup {
    private Context mContext;
    private CircleImageView bigImageView;//大圓
    private CircleImageView smallImageView;//小圓
    private float radiusScale = 0.2f;//小圖片與大圖片的比例,預設0.4
    int radius;//大圖片半徑
    private int smallRadius;//小圖片半徑
    private double angle = 45; //標識角度大小
    private boolean isprogress;//是否可以載入進度條,必須設定為true才能開啟
    private int progressCollor;//進度條顏色
    private int borderColor;//邊框顏色
    private int borderWidth;//邊框、進度條寬度
    private TextView textView;//識別符號為文字,用的地方比較少
    private boolean hintSmallView;//識別符號是否隱藏
    private Paint mBorderPaint;//邊框畫筆
    private Paint mProgressPaint;//進度條畫筆
    private float progresss;
    private Drawable bigImage;//大圖片
    private Drawable smallimage;//小圖片
    private int setprogressColor = 0;//動態設定進度條顏色值

    public IdentityImageView(Context context) {
        this(context, null);
    }

    public IdentityImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public IdentityImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        setWillNotDraw(false);//是的ondraw方法被執行


        addThreeView();

        initAttrs(attrs);
    }

其中addThreeView()方法就是例項化出我們需要的兩個圓形圖片和一個TextView;但是例項化出來的大小不是我們想要的,因此在onMeasure方法的結尾處,我們重新調整了一下各個控制元件的大小

 private void addThreeView() {

        bigImageView = new CircleImageView(mContext);//大圓

        smallImageView = new CircleImageView(mContext);//小圓

        textView = new TextView(mContext);//文字
        textView.setGravity(Gravity.CENTER);
        addView(bigImageView, 0, new LayoutParams(radius, radius));
        smallRadius = (int) (radius * radiusScale);
        addView(smallImageView, 1, new LayoutParams(smallRadius, smallRadius));
        addView(textView, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
        smallImageView.bringToFront();//使小圖片位於最上層
    }




//調整圖片的大小
    private void adjustThreeView() {
        bigImageView.setLayoutParams(new LayoutParams(radius, radius));
        smallRadius = (int) (radius * radiusScale);
        smallImageView.setLayoutParams(new LayoutParams(smallRadius, smallRadius));
        textView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
    }

這些步驟大家都很熟悉,需要注意的是setWillNotDraw(false)這個方法,因為這裡沒有載入佈局,如果沒有此方法,onDraw方法走不到,initAttrs(attrs)就是獲取自定義的屬性,屬性有:

     <declare-styleable name="IdentityImageView">
        <attr name="iciv_bigimage" format="reference"></attr><!--大圖片-->
        <attr name="iciv_smallimage" format="reference"></attr><!--小圖片-->
        <attr name="iciv_angle" format="float"></attr><!--標識角度-->
        <attr name="iciv_radiusscale" format="float"></attr><!--大小圖片比例-->
        <attr name="iciv_isprogress" format="boolean"></attr><!--是否有進度條-->
        <attr name="iciv_progress_collor" format="color|reference"></attr><!--進度條顏色-->
        <attr name="iciv_border_color" format="color|reference"></attr><!--邊框顏色-->
        <attr name="iciv_border_width" format="integer"></attr><!--邊框寬度-->
        <attr name="iciv_hint_smallimageview" format="boolean"></attr><!--是否隱藏小圖片-->
    </declare-styleable>

三、標識圖片的位置

屬性中smallRadius的值為:

smallRadius = (int) (radius * radiusScale);

要放入小圖片,肯定是在重寫onLayout方法,大圖片也是如此,重寫onLayout方法,大圖片容易實現,left和top為smallRadius,right和bottom都為控制元件寬減去totalwidth-smallRadius,關鍵是小圖片的座標如何確定,說肯定說不清楚,還是借一張西川地理位置圖比較容易知道情況,如下:


這裡寫圖片描述

只要得到圖中所標的下x,y座標,那麼就可以得到小圓左上角座標的具體值了,仔細看圖就能明白,這是個幾何問題,用到正弦餘弦,也就是三角函式的sin,cos,具體程式碼如下:

        double cos = Math.cos(angle * Math.PI / 180);//呼叫三角函式,這裡的angle為圖中的角a
        double sin = Math.sin(angle * Math.PI / 180);
        double left = totalwidth/2 + (radius * cos - smallRadius);
//圖中x的值
            double top = totalwidth/2 + (radius * sin - smallRadius);//圖中y的值

right和bottom加上小圓的直徑smallRadius*2就可以了;
所以onLayout方法重寫如下:



    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        //重點在於smallImageView的位置確定,預設為放在右下角,可自行拓展至其他位置


        double cos = Math.cos(angle * Math.PI / 180);
        double sin = Math.sin(angle * Math.PI / 180);
        double left = totalwidth/2 + (radius * cos - smallRadius);
        double top = totalwidth/2 + (radius * sin - smallRadius);
        int right = (int) (left + 2 * smallRadius);
        int bottom = (int) (top + 2 * smallRadius);
        bigImageView.layout(smallRadius, smallRadius, totalwidth-smallRadius, totalwidth-smallRadius);
        textView.layout((int) left, (int) top, right, bottom);
        smallImageView.layout((int) left, (int) top, right, bottom);

    }

    }

四、增加邊框、進度條

下面就剩下外圓和進度條了,不用想是在onDraw裡面用畫筆畫出來的,只不過這裡面有幾個坑:

  • Paint的setStrokeWidth方法,並不是往圓內側增加圓環(圓弧)寬度的,而是往外側增加一半,往內側增加一半。
  • add進來的View(比如兩個圖片View)顯示在畫出來的圓弧上面,時間緊迫就沒去搞明白怎麼回事。

    這兩個坑讓我調整了一下程式碼,把大圓的半徑減去了圓弧寬度的一半,這樣剛好,能看見圓弧,小圖又能遮蓋住圓弧,功能實現了就沒想那麼多,以後有時間再琢磨一下圖層關係。

外圓邊框和進度條的程式碼如下:


       canvas.drawCircle(totalwidth/2, totalwidth/2, radius - borderWidth / 2, mBorderPaint);//畫邊框,之所以半徑減半,是因為第一個坑


        RectF rectf = new RectF(smallRadius+borderWidth / 2, smallRadius+borderWidth / 2, getWidth() -smallRadius- borderWidth / 2, getHeight()-smallRadius - borderWidth / 2);
    //定義的圓弧的形狀和大小的範圍

        canvas.drawArc(rectf, (float) angle, progresss, false, mProgressPaint);
        //畫進度條,angle為起始角度,和上圖的a值一樣,progress為弧度角度,false為不顯示半徑線條

五、對外提供一些動態設定引數的方法

這裡沒涉及到點選滑動事件,所以沒有重寫分發事件一系列的方法,主要對外提供的方法有:

getBigCircleImageView();
getSmallCircleImageView();
//獲得大、小圖CircleImageView;拿到以後
//可以呼叫setImageDrawable、setImageResource()等方法直接設定圖片進去,也可以載入網路圖片設定進去,

 public void setAngle(int angles);//設定標識的角度
 public void setRadiusScale(float v);//設定標識的大小
 public void setIsprogress(boolean b) ;//設定是否可以有進度條
 public void setBorderColor(int color) ;//設定填充的顏色
 public void setProgressColor(int color);//設定進度條顏色
 public void setBorderWidth(int width) ;//設定進度條以及邊框寬度

這樣,帶進度條和標識功能的原型圖片就完成了,不熟悉自定義View的同學可以練一下,坑只有自己踩了才知道,原始碼已上傳github,點選檢視;有問題歡迎大家指正,共同進步,!

另外我的線上專案為空藝術,點選可以下載,朋友可以看下我的 頁面的頭像線上效果。

安卓問題交流群:661614986