編輯:關於Android編程
這是一個一言不合就手撸一個自定義View的任性時代,因此最近一段時間一直在學習自定義View相關的知識,也看了很多與此相關的博客,有句話叫做不要重復造輪子,別人寫好的直接拿過來改吧改吧,能用就行,但是,要想像那些任性的大牛一樣,分分鐘撸一個自定義View,就得不斷的重復造輪子,學習大神們的設計思路, 站在牛人的肩膀上不斷前行,每篇開篇之前都要啰嗦半天,急性子的童鞋可以直接跳過。看到yissan大牛寫了一篇自定義圓形進度條,思路很清晰,就照著也撸了一遍,果然是酸爽啊,在這裡非常感謝yissan大牛,哈哈。。。為了讓大家能一遍就看懂,我會把注釋寫的非常非常詳細,秒懂哦,,什麼??你不能秒懂。。注釋都寫的辣麼詳細了,面壁思過去。。哈哈
下面看下效果圖:

public CustomCircleProgress(Context context) {
this(context,null);
}
public CustomCircleProgress(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CustomCircleProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//獲取自定義屬性的值
TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.CustomCircleProgress);
//默認圓的顏色
mDefaultColor = array.getColor(R.styleable.CustomCircleProgress_progress_default_color, PROGRESS_DEFAULT_COLOR);
//進度條的顏色
mReachedColor = array.getColor(R.styleable.CustomCircleProgress_progress_reached_color, PROGRESS_REACHED_COLOR);
//默認圓的高度
mDefaultHeight = (int) array.getDimension(R.styleable.CustomCircleProgress_progress_default_height, mDefaultHeight);
//進度條的高度
mReachedHeight = (int) array.getDimension(R.styleable.CustomCircleProgress_progress_reached_height, mReachedHeight);
//圓的半徑
mRadius = (int) array.getDimension(R.styleable.CustomCircleProgress_circle_radius, mRadius);
//最後不要忘了回收 TypedArray
array.recycle();
//設置畫筆(new畫筆的操作一般不要放在onDraw方法中,因為在繪制的過程中onDraw方法會被多次調用)
setPaint();
//設置畫筆
private void setPaint() {
mPaint = new Paint();
//下面是設置畫筆的一些屬性
mPaint.setAntiAlias(true);//抗鋸齒
mPaint.setDither(true);//防抖動,繪制出來的圖要更加柔和清晰
mPaint.setStyle(Paint.Style.STROKE);//設置填充樣式
/**
* Paint.Style.FILL :填充內部
* Paint.Style.FILL_AND_STROKE :填充內部和描邊
* Paint.Style.STROKE :僅描邊
*/
mPaint.setStrokeCap(Paint.Cap.ROUND);//設置畫筆筆刷類型
}
當我們在xml文件中給這個view設置android:layout_width=”“android:layout_height=”“屬性為固定值、wrap_parent、match_parent 時,表明開發者向ViewGroup溝通表明我需要的空間。ViewGroup收到了開發者對View大小的說明,然後ViewGroup會綜合考慮自己的空間大小以及開發者的請求,然後生成兩個MeasureSpec對象(width與height)傳給View,這兩個對象是ViewGroup向子View提出的要求,就相當於告訴子View:“我已經與你的使用者(開發者)商量過了,現在把我們商量確定的結果告訴你,你的寬度不能違反width MeasureSpec對象的要求,你的高度不能違反height MeasureSpec對象的要求,現在,你趕緊根據這個要求確定下自己要多大空間,只許少,不許多哦。”對於超過ViewGroup為我們分配的空間時,就需要進行測量處理,然後再將處理後的結果反饋給ViewGroup,如果不是很了解的話可以點擊查看上一篇博客,有詳細的說明
/**
* 使用onMeasure方法是因為我們的自定義圓形View的一些屬性(如:進度條寬度等)都交給用戶自己去自定義了,所以我們需要去測量下
* 看是否符合要求
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int paintHeight = Math.max(mReachedHeight, mDefaultHeight);//比較兩數,取最大值
if(heightMode != MeasureSpec.EXACTLY){
//如果用戶沒有精確指出寬高時,我們就要測量整個View所需要分配的高度了,測量自定義圓形View設置的上下內邊距+圓形view的直徑+圓形描邊邊框的高度
int exceptHeight = getPaddingTop() + getPaddingBottom() + mRadius*2 + paintHeight;
//然後再將測量後的值作為精確值傳給父類,告訴他我需要這麼大的空間,你給我分配吧
heightMeasureSpec = MeasureSpec.makeMeasureSpec(exceptHeight, MeasureSpec.EXACTLY);
}
if(widthMode != MeasureSpec.EXACTLY){
//這裡在自定義屬性中沒有設置圓形邊框的寬度,所以這裡直接用高度代替
int exceptWidth = getPaddingLeft() + getPaddingRight() + mRadius*2 + paintHeight;
widthMeasureSpec = MeasureSpec.makeMeasureSpec(exceptWidth, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
我們需要考慮開發者有時會給View設置一些padding屬性
(1)這裡我們需要繪制默認的內部圓以及表示進度的外層圓弧,根據進度值的變化來繪制圓弧。在繪制外層表示進度的圓弧時,需要首先確定圓弧的外接矩形(進度也就成了內切圓)的坐標,如下圖所示

@Override
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
/**
* 這裡canvas.save();和canvas.restore();是兩個相互匹配出現的,作用是用來保存畫布的狀態和取出保存的狀態的
* 當我們對畫布進行旋轉,縮放,平移等操作的時候其實我們是想對特定的元素進行操作,但是當你用canvas的方法來進行這些操作的時候,其實是對整個畫布進行了操作,
* 那麼之後在畫布上的元素都會受到影響,所以我們在操作之前調用canvas.save()來保存畫布當前的狀態,當操作之後取出之前保存過的狀態,
* (比如:前面元素設置了平移或旋轉的操作後,下一個元素在進行繪制之前執行了canvas.save();和canvas.restore()操作)這樣後面的元素就不會受到(平移或旋轉的)影響
*/
canvas.save();
//為了保證最外層的圓弧全部顯示,我們通常會設置自定義view的padding屬性,這樣就有了內邊距,所以畫筆應該平移到內邊距的位置,這樣畫筆才會剛好在最外層的圓弧上
//畫筆平移到指定paddingLeft, getPaddingTop()位置
canvas.translate(getPaddingLeft(),getPaddingTop());
mPaint.setStyle(Paint.Style.STROKE);
//畫默認圓(邊框)的一些設置
mPaint.setColor(mDefaultColor);
mPaint.setStrokeWidth(mDefaultHeight);
canvas.drawCircle(mRadius,mRadius,mRadius,mPaint);
//畫進度條的一些設置
mPaint.setColor(mReachedColor);
mPaint.setStrokeWidth(mReachedHeight);
//根據進度繪制圓弧
float sweepAngle = getProgress() * 1.0f / getMax() * 360;
canvas.drawArc(new RectF(0, 0, mRadius * 2, mRadius *2), 0, sweepAngle, false, mPaint);//drawArc:繪制圓弧
canvas.restore();
}
我們做個定時器,讓進度條動起來
public class MainActivity extends AppCompatActivity {
private CustomCircleProgress circleProgress;
private int progress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
circleProgress = (CustomCircleProgress) findViewById(R.id.circleProgress);
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
if(progress >= 100){
progress = 0;
circleProgress.setProgress(0);
}else{
progress = circleProgress.getProgress();
circleProgress.setProgress(++progress);
}
}
};
timer.schedule(task,0,100);
}
}
這樣得到的效果圖是這樣的
vc/yoaPV4sDvztLDx9a70Oi9q9Syu6G1xMbwyrzOu9bDyejWw7PJLTkwtsi8tL/Jo6xjYW52YXMuZHJhd0FyYyhuZXcgUmVjdEYoMCwgMCwgbVJhZGl1cyA8L2VtPiAyLCBtUmFkaXVzICoyKSwgLTkwLCBzd2VlcEFuZ2xlLCBmYWxzZSwgbVBhaW50KaO7ztLDx9TZwLS/tM/C0Ke5+828PGJyIC8+DQo8aW1nIGFsdD0="這裡寫圖片描述" src="/uploadfile/Collfiles/20160831/201608310918321627.gif" title="\" />
完美,,哈哈,,,,
//繪制圓 public void drawCircle (float cx, float cy, float radius, Paint paint) //參數說明 /** * cx:圓心的x坐標。 cy:圓心的y坐標。 radius:圓的半徑。 paint:繪制時所使用的畫筆。 */
(2)接下來,我們開始繪制裡面的暫停(完成)狀態時的三角形,以及開啟狀態時的兩條豎線,首先我們通過枚舉的方式定義這兩種狀態,並提供set/get方法供外界調用。首先我們需要Path mPath = new Path();然後通過mPath.moveTo()確定三角形的第一個點的坐標,然後通過mPath.lineTo()鏈接其他幾個點的坐標,如果當我們設置畫筆的樣式為mPaint.setStyle(Paint.Style.STROKE);則我們需要執行close形成封閉的三角形,或者你也可以直接再來一條mPath.lineTo()再將第一個點的坐標給連接起來,這樣也形成了一個封閉的三角形。
//通過path路徑繪制三角形
mPath = new Path();
//讓三角形的長度等於圓的半徑(等邊三角形)
triangleLength = mRadius;
//繪制三角形,首先我們需要確定三個點的坐標
float firstX = (float) ((mRadius*2 - Math.sqrt(3.0) / 2 * mRadius) / 2);//左上角第一個點的橫坐標,根據勾三股四弦五定律,Math.sqrt(3.0)表示開方
//為了顯示的好看些,這裡微調下firstX橫坐標
float mFirstX = (float)(firstX + firstX*0.2);
float firstY = mRadius - triangleLength / 2;
//同理,依次可得出第二個點(左下角)第三個點的坐標
float secondX = mFirstX;
float secondY = (float) (mRadius + triangleLength / 2);
float thirdX = (float) (mFirstX + Math.sqrt(3.0) / 2 * mRadius);
float thirdY = mRadius;
mPath.moveTo(mFirstX,firstY);
mPath.lineTo(secondX,secondY);
mPath.lineTo(thirdX,thirdY);
mPath.lineTo(mFirstX,firstY);
然後我們在onDraw()方法中去判斷繪制不同狀態下的view
//有了path之後就可以在onDraw中繪制三角形的End和Starting狀態了
if(mStatus == Status.End){//未開始狀態,畫筆填充三角形
mPaint.setStyle(Paint.Style.FILL);
//設置顏色
mPaint.setColor(Color.parseColor("#01A1EB"));
//畫三角形
canvas.drawPath(mPath,mPaint);
}else{//正在進行狀態,畫兩條豎線
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(dp2px(5));
mPaint.setColor(Color.parseColor("#01A1EB"));
canvas.drawLine(mRadius*2/3, mRadius*2/3, mRadius*2/3, 2*mRadius*2/3, mPaint);
canvas.drawLine(2*mRadius - (mRadius*2/3), mRadius*2/3, 2*mRadius - (mRadius*2/3), 2*mRadius*2/3, mPaint);
}
最後是我們的MainActivity類
public class MainActivity extends AppCompatActivity {
private CustomCircleProgress circleProgress;
private int progress;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case PROGRESS_CIRCLE_STARTING:
progress = circleProgress.getProgress();
circleProgress.setProgress(++progress);
if(progress >= 100){
handler.removeMessages(PROGRESS_CIRCLE_STARTING);
progress = 0;
circleProgress.setProgress(0);
circleProgress.setStatus(CustomCircleProgress.Status.End);//修改顯示狀態為完成
}else{
//延遲100ms後繼續發消息,實現循環,直到progress=100
handler.sendEmptyMessageDelayed(PROGRESS_CIRCLE_STARTING, 100);
}
break;
}
}
};
public static final int PROGRESS_CIRCLE_STARTING = 0x110;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
circleProgress = (CustomCircleProgress) findViewById(R.id.circleProgress);
circleProgress.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(circleProgress.getStatus() == CustomCircleProgress.Status.Starting){//如果是開始狀態
//點擊則變成關閉暫停狀態
circleProgress.setStatus(CustomCircleProgress.Status.End);
//注意,當我們暫停時,同時還要移除消息,不然的話進度不會被停止
handler.removeMessages(PROGRESS_CIRCLE_STARTING);
}else{
//點擊則變成開啟狀態
circleProgress.setStatus(CustomCircleProgress.Status.Starting);
Message message = Message.obtain();
message.what = PROGRESS_CIRCLE_STARTING;
handler.sendMessage(message);
}
}
});
}
}
自定義View 之利用ViewPager 實現畫廊效果(滑動放大縮小)
基本介紹畫廊在很多的App設計中都有,如下圖所示:該例子是我沒事的時候寫的一個小項目,具體源碼地址請訪問https://github.com/AlexSmille/Yin
Android之單線程下載與多線程下載
概述:單線程下載很簡單,就是開啟一個線程去下載資源再進行本地保存;多線程下載是通過RandomAccessFile(隨機文件讀寫操作類)來設置每個線程讀取文件的起始點位置
Android自定義view實現進度條指示效果
先看看效果圖:首先是布局文件<FrameLayout android:layout_width=match_parent android:layout_margin
Fragment生命周期詳解
1. Fragment概述Fragment從Android v3.0版本開始引入隨著界面布局的復雜化,處理起來也更加的復雜,引入Fragment可以把activity拆分