*本篇文章已授權微信公衆号 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 = + radiusScale;
int radius2 = totalwidth / ;
radius = (int) (radius2 / scale);
break;
case MeasureSpec.AT_MOST: //說明在布局檔案中使用的是wrap_content:
//這時我們也寫死寬高
radius = ;
totalwidth = (int) ((radius + radius * radiusScale) * );
break;
default:
radius = ;
totalwidth = (int) ((radius + radius * radiusScale) * );
break;
}
setMeasuredDimension(totalwidth, totalwidth);
adjustThreeView();
}
神之憤怒
二、初始化IdentityImageView:
public class IdentityImageView extends ViewGroup {
private Context mContext;
private CircleImageView bigImageView;//大圓
private CircleImageView smallImageView;//小圓
private float radiusScale = f;//小圖檔與大圖檔的比例,預設0.4
int radius;//大圖檔半徑
private int smallRadius;//小圖檔半徑
private double angle = ; //辨別角度大小
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 = ;//動态設定進度條顔色值
public IdentityImageView(Context context) {
this(context, null);
}
public IdentityImageView(Context context, AttributeSet attrs) {
this(context, attrs, );
}
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, , new LayoutParams(radius, radius));
smallRadius = (int) (radius * radiusScale);
addView(smallImageView, , 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的值為:
要放入小圖檔,肯定是在重寫onLayout方法,大圖檔也是如此,重寫onLayout方法,大圖檔容易實作,left和top為smallRadius,right和bottom都為控件寬減去totalwidth-smallRadius,關鍵是小圖檔的坐标如何确定,說肯定說不清楚,還是借一張西川地理位置圖比較容易知道情況,如下:
隻要得到圖中所标的下x,y坐标,那麼就可以得到小圓左上角坐标的具體值了,仔細看圖就能明白,這是個幾何問題,用到正弦餘弦,也就是三角函數的sin,cos,具體代碼如下:
double cos = Math.cos(angle * Math.PI / );//調用三角函數,這裡的angle為圖中的角a
double sin = Math.sin(angle * Math.PI / );
double left = totalwidth/ + (radius * cos - smallRadius);
//圖中x的值
double top = totalwidth/ + (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 / );
double sin = Math.sin(angle * Math.PI / );
double left = totalwidth/ + (radius * cos - smallRadius);
double top = totalwidth/ + (radius * sin - smallRadius);
int right = (int) (left + * smallRadius);
int bottom = (int) (top + * 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/, totalwidth/, radius - borderWidth / , mBorderPaint);//畫邊框,之是以半徑減半,是因為第一個坑
RectF rectf = new RectF(smallRadius+borderWidth / , smallRadius+borderWidth / , getWidth() -smallRadius- borderWidth / , getHeight()-smallRadius - borderWidth / );
//定義的圓弧的形狀和大小的範圍
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