星空發現圖(仿蝸牛夢話圈)

最終效果

直接使用

  1. 繼承 BaseAdapter 實現onCreateCenter(ViewGroup parent) onCreateChild(ViewGroup parent, T t)方法設置中心View,子View.
  2. DreamLayout實例 調用 setAdapter(BaseAdapter baseAdapter) 方法.
  3. 調用BaseAdapteraddChild(T t),removeChild(T t)增刪子View.

需求分解

  • 背景類水波紋的擴散效果git

    • 初始化

    初始化沒啥好說的,就是一些畫筆設置,點位初始化,由於部分初始化與控件大小相關,所以要講初始化放在尺寸確認後,瞭解一下View的關鍵生命週期構造函數() --> onFinishInflate() --> onAttachedToWindow() --> onMeasure() --> onSizeChanged() --> onLayout() --> onDraw() --> onDetackedFromWindow() 當onSizeChanged以後,佈局大小肯定下來,所以在此處初始化.github

    • 環形漸變效果 使用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%,超出最大值,從新開始,模擬水波紋效果canvas

    • 繪製圖形,使用畫布縮放繪製不一樣大小的圓環;

    本來直接想用drawCircle()繪製不一樣大小的圓,出現一個問題,須要爲不一樣大小的圓從新 設置環形漸變,而onDraw()是一個頻繁操做,不建議建立對象,所以想到經過縮放畫布的方式來實現繪製不一樣大小的漸變圓環.bash

    // scale 描述圖片放大比例;
        // 將畫布放大scale 比例後,繪製一樣大小的圓,還原畫布,便可得放大scale倍的圓
        canvas.save();
        canvas.scale(scale, scale, width / 2, height / 2);
        canvas.drawCircle(width / 2, height / 2, mRadiusMax, mLayoutPaint);
        canvas.restore();
    複製代碼
  • 隨機顯示位置的子控件dom

    • 隨機點位:保證不重疊;

    對於圓形圖,不重疊即保證生成的新點與已存在的點距離大於二者半徑和 隨機生成點,計算其與已存在在的控件比較,不符合條件,從新生成,超出重試次數即認爲無足夠空間.ide

    /**
     * 獲取隨機點,根據父子空間半徑判斷隨機點是否有足夠空間可用
     * @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);
        }
    複製代碼

大功告成

至此爲止,這個小布局的核心功能就完成了. 源碼導航函數

相關文章
相關標籤/搜索