安卓開發:仿微博自定義帶進度條和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