星空發現圖(仿蝸牛夢話圈)
阿新 • • 發佈:2018-12-19
最終效果
直接使用
- 繼承
BaseAdapter
實現onCreateCenter(ViewGroup parent)
onCreateChild(ViewGroup parent, T t)
方法設定中心View,子View. - DreamLayout例項 呼叫
setAdapter(BaseAdapter baseAdapter)
方法. - 呼叫
BaseAdapter
的addChild(T t)
,removeChild(T t)
增刪子View.
需求分解
-
背景類水波紋的擴散效果
- 初始化
初始化沒啥好說的,就是一些畫筆設定,點位初始化,因為部分初始化與控制元件大小相關,因此要講初始化放在尺寸確認後,瞭解一下View的關鍵生命週期
建構函式() --> onFinishInflate() --> onAttachedToWindow() --> onMeasure() --> onSizeChanged() --> onLayout() --> onDraw() --> onDetackedFromWindow()
- 環形漸變效果 使用RadialGradient
//圓心為畫布中心,半徑為 mRadiusMax,半徑80%處向外漸變為白色 RadialGradient gradient = new RadialGradient(width / 2, height / 2, mRadiusMax, new int[]{centerColor, centerColor, edgeColor}, new float[]{0f, .8f, 1.0f}, Shader.TileMode.MIRROR); mLayoutPaint.setShader(gradient); 複製程式碼
- 使用handler 定時,更新圓直徑,模擬水波紋效果
初始化三個大小不等的圓,每次放大2%,超出最大值,重新開始,模擬水波紋效果
- 繪製圖形,使用畫布縮放繪製不同大小的圓環;
原本直接想用
drawCircle()
繪製不同大小的圓,出現一個問題,需要為不同大小的圓重新 設定環形漸變,而onDraw()
是一個頻繁操作,不建議建立物件,因此想到通過縮放畫布的方式來實現繪製不同大小的漸變圓環.// scale 描述圖片放大比例; // 將畫布放大scale 比例後,繪製同樣大小的圓,還原畫布,即可得放大scale倍的圓 canvas.save(); canvas.scale(scale, scale, width / 2, height / 2); canvas.drawCircle(width / 2, height / 2, mRadiusMax, mLayoutPaint); canvas.restore(); 複製程式碼
-
隨機顯示位置的子控制元件
- 隨機點位:保證不重疊;
對於圓形圖,不重疊即保證生成的新點與已存在的點距離大於兩者半徑和 隨機生成點,計算其與已存在在的控制元件比較,不符合條件,重新生成,超出重試次數即認為無足夠空間.
/** * 獲取隨機點,根據父子空間半徑判斷隨機點是否有足夠空間可用 * @param rad 子控制元件半徑 * @param radMax 父佈局半徑 * @param centerX 父佈局中點 x * @param centerY 父佈局中點 y * @return 一個可用的隨機點位 */ private Point getRandomPoint(int rad, int radMax, int centerX, int centerY) { int x = 0; int y = 0; int counter = 0; boolean conflict; Random random = new Random(); while (counter < 100000) { counter++; x = (int) (getMeasuredWidth() / 2 - radMax + rad + random.nextInt( 2 * radMax - 2 * rad)); y = (int) (getMeasuredHeight() / 2 - radMax + rad + random.nextInt( 2 * radMax - 2 * rad)); conflict = false; // 判斷與其他子控制元件是否衝突 for (Point p : mChildrenPoints) { int dis = (int) Math.sqrt(Math.pow(p.x - x, 2) + Math.pow(p.y - y, 2)); if (dis < 2 * rad * (1 + mBlankPer)) {//子控制元件大小一致,設定兩個子控制元件中點距離最小為 半徑和的(1+mBlankPer)倍 conflict = true; break; } } if (!conflict) {// 判斷與中心點衝突 int dis = (int) Math.sqrt(Math.pow(centerX - x, 2) + Math.pow(centerY - y, 2)); if (dis < (rad + mCenterRadius) * ((1 + mBlankPer))) conflict = true; } if (!conflict) return new Point(x, y); } Log.e(TAG, "getRandomPoint: 無剩餘空間"); return new Point(0, 0); } 複製程式碼
- 測量大小:交給子控制元件自己測量,保證最大值不超過佈局的寬高較小值即可
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int contentWidth = MeasureSpec.getSize(widthMeasureSpec); int contentHeight = MeasureSpec.getSize(heightMeasureSpec); int len = Math.min(contentWidth, contentHeight); measureChildren(MeasureSpec.makeMeasureSpec(len, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(len, MeasureSpec.AT_MOST)); } 複製程式碼
- 佈局 onLayout()
for (int i = 1; i < getChildCount(); i++) { View view = getChildAt(i); int rad = Math.max(view.getMeasuredWidth(), view.getMeasuredHeight()) / 2; Point position; // 資料只支援末尾插入,可直接通過列表長度判斷是否是新增點 if (i > mChildrenPoints.size()) {// 新增的控制元件,設定隨機點 position = getRandomPoint(rad, mRadiusMax, getMeasuredWidth() / 2, getMeasuredHeight() / 2); mChildrenPoints.add(position); } else {//已存在控制元件,直接從列表取 position = mChildrenPoints.get(i - 1); } int x = position.x; int y = position.y; view.layout(x - rad, y - rad, x + rad, y + rad); } 複製程式碼
大功告成
至此為止,這個小布局的核心功能就完成了. 原始碼導航