自定義View之案列篇(一):鞭炮

首先給各位道個歉,公司加班已有兩個多月,博客也遲遲沒有更新。還非常感謝認(rèn)真閱讀博客并提出錯誤的地方的童鞋,我也非常鼓勵這種做法,對任何有疑問的地方,大膽提出。給你們點(diǎn)個贊。

老規(guī)矩,先來看看類似過年放的鞭炮的效果圖:

block

學(xué)習(xí)博客也是學(xué)習(xí)一種變通的思想,能夠舉一反三,才能掌握真正的精髓。

這里留下一個小小的挑戰(zhàn):

block

怎么獲取菱形區(qū)域的點(diǎn)擊事件?

我這里是以 Region (區(qū)域)來解決的,不知道你是否有更好的方案?

鞭炮布局(BlockFrameLayout)

分析效果圖,可以看出魔方布局是由一塊塊小的菱形按照某種規(guī)律組合而成的,我們這里以繁化簡,先來看看一塊小的菱形:

block

接著看看三塊菱形組成的圖案:

block

看看最后的效果圖:

block

是不是已經(jīng)發(fā)現(xiàn)規(guī)律了?。繉Φ?,以三塊菱形為一組,從上往下就組成了最終的圖案。

核心思想:分析前三塊菱形和后三塊菱形的坐標(biāo)變化。

相信你已經(jīng)找到了:

    if ((i + 1) % 3 == 1) { //第一塊
        startX = getWidth() / 2 - mChildSize / 2;
        startY = mChildSize * bulge;
    } else if ((i + 1) % 3 == 2) { //第二塊
        startX = getWidth() / 2 - mChildSize;
        startY = mChildSize * bulge + mChildSize / 2;
    } else if ((i + 1) % 3 == 0) { //第三塊
        startX = getWidth() / 2;
        startY = mChildSize * bulge + mChildSize / 2;
        bulge++;
    }

那么 onLayout 方法:

   final int childCount = getChildCount();
   int startX = 0;
   int startY = 0;
   int bulge = 0;
   for (int i = 0; i < childCount; i++) {
       View childView = getChildAt(i);
       if (childView.getVisibility() == GONE) {
           continue;
       }
       if ((i + 1) % 3 == 1) {
           startX = getWidth() / 2 - mChildSize / 2;
           startY = mChildSize * bulge;
       } else if ((i + 1) % 3 == 2) {
           startX = getWidth() / 2 - mChildSize;
           startY = mChildSize * bulge + mChildSize / 2;
       } else if ((i + 1) % 3 == 0) {
           startX = getWidth() / 2;
           startY = mChildSize * bulge + mChildSize / 2;
           bulge++;
       }
       childView.layout(startX, startY, startX + mChildSize, startY + mChildSize);
   }

如果對于自定義 ViewGroup 流程有什么疑問的童鞋,請查看我自定義系類前面的幾篇博客。

菱形(BlockView )

BlockView 類比較簡單,繪制菱形(Path路徑),大家一起來看看 onDraw 方法:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mPath.reset();

        mPath.moveTo(getWidth() / 2, 0);
        mPath.lineTo(0, getHeight() / 2);
        mPath.lineTo(getWidth() / 2, getHeight());
        mPath.lineTo(getWidth(), getHeight() / 2);

        mPath.close();
        canvas.drawPath(mPath, mPaint);

        mTextPaint.setTextSize(getWidth() / 4);
        mMetrics = mTextPaint.getFontMetrics();
        mTextPaint.setTextAlign(Paint.Align.CENTER);

        canvas.drawText(mText, getWidth() / 2, getHeight() / 2 +
                (mMetrics.bottom - mMetrics.top) / 2 - mMetrics.bottom, mTextPaint);

    }

接著需要處理觸摸點(diǎn)擊事件,你肯定會想到重寫 onTouchEvent(觸摸事件) 方法?不過這里又有一個疑問,View 跟 View 之間有重疊的部分,onTouchEvent 方法返回 true(消費(fèi)事件) 可能會引起下層的 View 的點(diǎn)擊事件失效?

那么我們對返回值不進(jìn)行處理。

具體如下:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                float x = event.getX();
                float y = event.getY();

                Region region = new Region();
                region.setPath(mPath, new Region(0, 0, getWidth(), getHeight()));

                if (mOnClickListener != null) {
                    if (region.contains((int) x, (int) y)) {
                        mOnClickListener.BlockOnClickListener(mText);
                        //mClickEnable = false;
                    }
                }
                break;
        }
        return super.onTouchEvent(event);
    }

super.onTouchEvent(event) 默認(rèn)的返回值。這樣就很巧妙的處理了不規(guī)則圖形的點(diǎn)擊事件以及沖突的事件傳遞問題。

源碼地址。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容