編輯:關於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); } } }); } }
基本介紹畫廊在很多的App設計中都有,如下圖所示:該例子是我沒事的時候寫的一個小項目,具體源碼地址請訪問https://github.com/AlexSmille/Yin
概述:單線程下載很簡單,就是開啟一個線程去下載資源再進行本地保存;多線程下載是通過RandomAccessFile(隨機文件讀寫操作類)來設置每個線程讀取文件的起始點位置
先看看效果圖:首先是布局文件<FrameLayout android:layout_width=match_parent android:layout_margin
1. Fragment概述Fragment從Android v3.0版本開始引入隨著界面布局的復雜化,處理起來也更加的復雜,引入Fragment可以把activity拆分