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

首先给各位道个歉,公司加班已有两个多月,博客也迟迟没有更新。还非常感谢认真阅读博客并提出错误的地方的童鞋,我也非常鼓励这种做法,对任何有疑问的地方,大胆提出。给你们点个赞。

老规矩,先来看看类似过年放的鞭炮的效果图:

自定义View之案列篇(一):鞭炮_第1张图片
block

学习博客也是学习一种变通的思想,能够举一反三,才能掌握真正的精髓。

这里留下一个小小的挑战:

自定义View之案列篇(一):鞭炮_第2张图片
block

怎么获取菱形区域的点击事件?

我这里是以 Region (区域)来解决的,不知道你是否有更好的方案?

鞭炮布局(BlockFrameLayout)

分析效果图,可以看出魔方布局是由一块块小的菱形按照某种规律组合而成的,我们这里以繁化简,先来看看一块小的菱形:

自定义View之案列篇(一):鞭炮_第3张图片
block

接着看看三块菱形组成的图案:

自定义View之案列篇(一):鞭炮_第4张图片
block

看看最后的效果图:

自定义View之案列篇(一):鞭炮_第5张图片
block

是不是已经发现规律了啊?对的,以三块菱形为一组,从上往下就组成了最终的图案。

核心思想:分析前三块菱形和后三块菱形的坐标变化。

相信你已经找到了:

    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);

    }

接着需要处理触摸点击事件,你肯定会想到重写 onTouchEvent(触摸事件) 方法?不过这里又有一个疑问,View 跟 View 之间有重叠的部分,onTouchEvent 方法返回 true(消费事件) 可能会引起下层的 View 的点击事件失效?

那么我们对返回值不进行处理。

具体如下:

    @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) 默认的返回值。这样就很巧妙的处理了不规则图形的点击事件以及冲突的事件传递问题。

源码地址。

你可能感兴趣的:(自定义View之案列篇(一):鞭炮)