前语

在之前的文章中,通过LED作用马赛克作用两篇文章,介绍了分片制作的作用的办法和原理,通过这两篇文章,信任大家都已经了解了分片制作的思路。其实分片制作不只仅能完成LED、马赛克等特殊作用,实际上相似百叶窗、图片对角线锯齿过渡等,许多PPT中存在的特效,基本上也是依照这种原理来完成的。

分片能够有许多种意想不到的效,咱们再来说一下分片特点:

  • [1] 按一定的距离、大小、视点对区域进行对一张图片或许区域裁剪或许提取区域图画
  • [2] 对提取出来的区域进行一系列改换,如百叶窗、微信摇一摇等
  • [3] 被裁剪的区域能够还原回去

技术前景

其实单纯的分片能够做一些瓦片作用,当然还能够做一些组合作用,下面是一个github开源项目(Camera2DApplication)运用Camera和图片分片完成的作用,这个过程中对一张图片进行分片制作。

Android 图片分片过渡作用

代码中的逻辑不是很复杂,本质上便是运用2张图片完成的,咱们先来看下代码完成,作者的代码很认真,注释都写了,涉及postTranslate比较难明的操作我也进行了微调。

/**
 * 3d旋转作用
 *
 * @param canvas
 */
private void drawModeNormal(Canvas canvas) {
    //VERTICAL时运用rotateY,HORIZONTAL时运用rotateX
    if (orientation == VERTICAL) {
        //如果是行进,则画当时图,撤退则画上一张图,注释用的是行进情况
        matrix.reset();
        camera.save();
        //旋转视点 0 - -maxDegress 
        camera.rotateX(-degress);
        camera.getMatrix(matrix);
        camera.restore();
        //绕着图片top旋转
        matrix.preTranslate(-viewWidth / 2f, 0);
        //旋转轴向下平移,则图片也向下平移
        matrix.postTranslate(viewWidth / 2f, rotatePivotY);
        //如果是行进,则画当时图,撤退则画上一张图,由于撤退时,这里画的是动画下方出来的图片,而下方的图片是前一张图
        canvas.drawBitmap(getBitmapScale(bitmapResourceIds.get(isForward ? currentIndex : preIndex), viewWidth, viewHeight),
                matrix, mPaint);
        //在处理下一张图片
        matrix.reset();
        camera.save();
        //旋转视点 maxDegress - 0
        camera.rotateX(maxDegress - degress);
        camera.getMatrix(matrix);
        camera.restore();
        //绕着图片bottom旋转
        matrix.preTranslate(-viewWidth / 2f, -viewHeight);
        //旋转轴向下平移,则图片也向下平移
        matrix.postTranslate(viewWidth / 2f, rotatePivotY);
        //如果是行进,则画下一张图,撤退则画当时图,撤退时,这边代码画的是动画上方的图片,上方的图片是当时图片
        canvas.drawBitmap(getBitmapScale(bitmapResourceIds.get(isForward ? nextIndex : currentIndex), viewWidth, viewHeight),
                matrix, mPaint);
    } else {
        //如果是行进,则画当时图,撤退则画上一张图,注释用的是行进情况
        matrix.reset();
        camera.save();
        //旋转视点 0 - maxDegress 
        camera.rotateY(degress);
        camera.getMatrix(matrix);
        camera.restore();
        //绕着图片left旋转
        matrix.preTranslate(0, -viewHeight / 2);
        //旋转轴向右平移,则图片也向右平移
        matrix.postTranslate(rotatePivotX, viewHeight / 2);
        //如果是行进,则画当时图,撤退则画上一张图,由于撤退时,这里画的是动画右方出来的图片,而右方的图片是前一张图
        canvas.drawBitmap(getBitmapScale(bitmapResourceIds.get(isForward ? currentIndex : preIndex), viewWidth, viewHeight),
                matrix, mPaint);
        //在处理下一张图片
        matrix.reset();
        camera.save();
        //旋转视点 -maxDegress - 0
        camera.rotateY(-maxDegress + degress);
        camera.getMatrix(matrix);
        camera.restore();
        //绕着图片right旋转
        matrix.preTranslate(-viewWidth, -viewHeight / 2f);
        //旋转轴向右平移,则图片也向右平移
        matrix.postTranslate(rotatePivotX, viewHeight / 2f);
        //如果是行进,则画下一张图,撤退则画当时图,撤退时,这边代码画的是动画左方的图片,左方的图片是当时图片
        canvas.drawBitmap(getBitmapScale(bitmapResourceIds.get(isForward ? nextIndex : currentIndex), viewWidth, viewHeight),
                matrix, mPaint);
    }
}

分片操作

下面是分片操作,这个地方其实能够不必创建Bitmap缓存,创建Path就行,制作时对Path区域运用Shader贴图即可。

private Bitmap getBitmapScale(int resId, float width, float height) {
    if (ImageCache.getInstance().getBitmapFromMemCache(String.valueOf(resId)) != null) {
        return ImageCache.getInstance().getBitmapFromMemCache(String.valueOf(resId));
    }
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId);
    //创建分片
    Bitmap bitmapDst = Bitmap.createScaledBitmap(bitmap, (int) width, (int) height, false);
    bitmap.recycle();
    ImageCache.getInstance().addBitmapToMemoryCache(String.valueOf(resId)
            , bitmapDst);
    return bitmapDst;
}

小试一下

咱们这里通过一个简略的Demo,完成一种特效,这次咱们运用网格矩阵分片。说到矩阵,许多人面试的时分都会遇到一些算法题,比较幸运的人遇到的是矩阵旋转90度、逆时针打印矩阵、矩阵孤岛问题、从左上角开端进行矩阵元素搜索,命运稍差的会遇到由外到里顺时针打印矩阵和斜对角打印矩阵,后面两种看似简略的问题实际上做起来并不随手,有点扯远了,咱们来看看作用。

你没看错,这次遇到了算法问题,我这边用的空间换取时刻的办法。

图画分片

将图片分片,计算出网格的列和行

int col = (int) Math.ceil(mBitmaps[index].getWidth() / blockWidth);
int row = (int) Math.ceil(mBitmaps[index].getHeight() / blockWidth);

分片算法

这个算法实际上是每次将列数 +1,然后按对角切割,把契合的区域增加到path中

int x = xPosition;
int y = 0;
while (x >= 0 && y <= row) {
    if (x < col && y < row) {
        dstRect.set((int) (x * blockWidth), (int) (y * blockWidth), (int) (x * blockWidth + blockWidth), (int) (y * blockWidth + blockWidth));
      //  bitmapCanvas.drawBitmap(mBitmaps[index], dstRect, dstRect, mCommonPaint);
        path.addRect(dstRect, Path.Direction.CCW);  //加入网格分片
    }
    x--;
    y++;
}

Path 途径贴图

  • Path过程中咱们增加的rect是闭合区域,是能够贴图的,当然,一般有三种办法:
  • Path的贴图一般运用 clipPath对图片裁剪然后贴图,当然还有将对应的图片区域制作到View上
  • Path 是Rect,依照Rect将图片区域制作到Rect区域
  • 运用BitmapShader一次性制作

实际上咱们应该尽可能运用Bitmap,由于BitmapShader唯一是不存在锯齿功能比较好的制作办法。

int save = bitmapCanvas.save();
mCommonPaint.setShader(new BitmapShader(mBitmaps[index], Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
bitmapCanvas.drawPath(path,mCommonPaint);
bitmapCanvas.restoreToCount(save);

其实咱们的核心代码到这里就完毕了,咱们能够看到,分片能够的含义很重要的,当然,凭借其他东西也能够完成,不过代码完成的好处是能够编辑和交互,不是所有的动画都能够发生交互。

到此,咱们还能够对今日的demo增加一些想象

  • 从中间外扩作用
  • 奇偶行切换作用
  • 国际象棋黑白格子改换作用
  • ……

总结

这是咱们的第三篇关于图片分片特效的博客,期望通过一些了的文章,了解一些技术,往往看似巨大上的作用,其实便是通过普普通通的办法叠加在一起的,当然,让你的技术承载你的想象,才是最重要的。

本篇demo悉数代码

实际上代码贴太多很可能没人看,可是依照惯例,咱们给出完好代码。

public class TilesView extends View {
    private final DisplayMetrics mDM;
    private TextPaint mCommonPaint;
    private RectF mainRect = new RectF();
    private BitmapCanvas bitmapCanvas; //Canvas 封装的
    private Bitmap[] mBitmaps;
    private RectF dstRect = new RectF();
    Path path = new Path();
    private float blockWidth = 50f;
    private int xPosition = -2;
    private int index = 0;
    private boolean isTicking = false;
    public TilesView(Context context) {
        this(context, null);
    }
    public TilesView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public TilesView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mDM = getResources().getDisplayMetrics();
        initPaint();
    }
    private void initPaint() {
        //不然提供给外部纹路制作
        mCommonPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        mCommonPaint.setAntiAlias(true);
        mCommonPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mCommonPaint.setStrokeCap(Paint.Cap.ROUND);
        mCommonPaint.setFilterBitmap(true);
        mCommonPaint.setDither(true);
        mBitmaps = new Bitmap[3];
        mBitmaps[0] = decodeBitmap(R.mipmap.mm_013);
        mBitmaps[1] = decodeBitmap(R.mipmap.mm_014);
        mBitmaps[2] = decodeBitmap(R.mipmap.mm_015);
    }
    private Bitmap decodeBitmap(int resId) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inMutable = true;
        return BitmapFactory.decodeResource(getResources(), resId, options);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        if (widthMode != MeasureSpec.EXACTLY) {
            widthSize = mDM.widthPixels / 2;
        }
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if (heightMode != MeasureSpec.EXACTLY) {
            heightSize = widthSize / 2;
        }
        setMeasuredDimension(widthSize, heightSize);
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (bitmapCanvas != null && bitmapCanvas.bitmap != null && !bitmapCanvas.bitmap.isRecycled()) {
            bitmapCanvas.bitmap.recycle();
        }
        bitmapCanvas = null;
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        if (width < 1 || height < 1) {
            return;
        }
        if (bitmapCanvas == null || bitmapCanvas.bitmap == null || bitmapCanvas.bitmap.isRecycled()) {
            bitmapCanvas = new BitmapCanvas(Bitmap.createBitmap(mBitmaps[index].getWidth(), mBitmaps[index].getHeight(), Bitmap.Config.ARGB_8888));
        }
        int nextIndex = (index + 1) % mBitmaps.length;
        canvas.drawBitmap(mBitmaps[nextIndex],0,0,mCommonPaint);
        int col = (int) Math.ceil(mBitmaps[index].getWidth() / blockWidth);
        int row = (int) Math.ceil(mBitmaps[index].getHeight() / blockWidth);
        mCommonPaint.setStyle(Paint.Style.FILL);
     //   path.reset();
//        for (int x = 0; x < row; x++) {
//            for (int y = 0; y < col; y++) {
//                gridRectF.set(x * blockWidth, y * blockWidth, x * blockWidth + blockWidth, y * blockWidth + blockWidth);
//                canvas.drawRect(gridRectF, mCommonPaint);
//                path.addRect(gridRectF, Path.Direction.CCW);
//            }
//        }
        diagonalEffect(col,row,xPosition,path);
        canvas.drawBitmap(bitmapCanvas.bitmap, 0, 0, mCommonPaint);
        if (isTicking && xPosition >= 0 && xPosition < col * 2) {
            clockTick();
        } else if(isTicking){
            xPosition = -1;
            index = nextIndex;
            isTicking = false;
        }
    }
    private void diagonalEffect(int col, int row, int xPosition,Path path) {
        int x = xPosition;
        int y = 0;
        while (x >= 0 && y <= row) {
            if (x < col && y < row) {
                dstRect.set((int) (x * blockWidth), (int) (y * blockWidth), (int) (x * blockWidth + blockWidth), (int) (y * blockWidth + blockWidth));
              //  bitmapCanvas.drawBitmap(mBitmaps[index], dstRect, dstRect, mCommonPaint);
                path.addRect(dstRect, Path.Direction.CCW);  //加入网格分片
            }
            x--;
            y++;
        }
        int save = bitmapCanvas.save();
        mCommonPaint.setShader(new BitmapShader(mBitmaps[index], Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
        bitmapCanvas.drawPath(path,mCommonPaint);
        bitmapCanvas.restoreToCount(save);
    }
    public void tick() {
        isTicking = true;
        xPosition = -1;
        path.reset();
        clockTick();
    }
    private void clockTick() {
        xPosition += 1;
        postInvalidateDelayed(16);
    }
    public float dp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDM);
    }
    public float sp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, dp, mDM);
    }
    static class BitmapCanvas extends Canvas {
        Bitmap bitmap;
        public BitmapCanvas(Bitmap bitmap) {
            super(bitmap);
            //继承在Canvas的制作是软制作,因此理论上能够制作出阴影
            this.bitmap = bitmap;
        }
        public Bitmap getBitmap() {
            return bitmap;
        }
    }
}