前语
在之前的文章中,通过LED作用、马赛克作用两篇文章,介绍了分片制作的作用的办法和原理,通过这两篇文章,信任大家都已经了解了分片制作的思路。其实分片制作不只仅能完成LED、马赛克等特殊作用,实际上相似百叶窗、图片对角线锯齿过渡等,许多PPT中存在的特效,基本上也是依照这种原理来完成的。
分片能够有许多种意想不到的效,咱们再来说一下分片特点:
- [1] 按一定的距离、大小、视点对区域进行对一张图片或许区域裁剪或许提取区域图画
- [2] 对提取出来的区域进行一系列改换,如百叶窗、微信摇一摇等
- [3] 被裁剪的区域能够还原回去
技术前景
其实单纯的分片能够做一些瓦片作用,当然还能够做一些组合作用,下面是一个github开源项目(Camera2DApplication)运用Camera和图片分片完成的作用,这个过程中对一张图片进行分片制作。
代码中的逻辑不是很复杂,本质上便是运用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;
}
}
}