Canvas实现Reveal揭露特效
Posted 花花young
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Canvas实现Reveal揭露特效相关的知识,希望对你有一定的参考价值。
前言
在android官方对Canvas描述是:Canvas类容纳所有和Draw(绘制)相关的方法。为了去Draw,你需要具备四个要素,1是Bitmap用来承载像素信息、2是Canvas用来管理Draw相关方法、3是绘制基元(例如,Rect,Path,text,Bitmap)、4是一个画笔(用来描述图像的颜色和风格)
本文之前有关于Paint的文章
绘图不可或缺的画笔Paint-使用篇
绘图不可或缺的画笔Paint-渲染篇
绘图不可或缺的画笔Paint-滤镜篇
Part 1、Canvas基础讲解
效果~
我们不妨对上面的图形进行分析,首先我们需要绘制一个扁平的菱形在原点的位置
LinearGradient lg = new LinearGradient(0f, 0f, (float) (2 * (Math.cos(angle) * LONG_SIZE)), 0f, Color.GREEN, Color.RED, Shader.TileMode.REPEAT);
paint.setShader(lg);
path = new Path();
path.reset();
path.moveTo(0, 0);
path.lineTo((float) (Math.cos(angle) * LONG_SIZE), (float) (Math.sin(angle) * LONG_SIZE));
path.lineTo((float) (2 * (Math.cos(angle) * LONG_SIZE)), 0);
canvas.drawPath(path, paint);
path.reset();
path.moveTo(0, 0);
path.lineTo((float) (Math.cos(angle) * LONG_SIZE), -(float) (Math.sin(angle) * LONG_SIZE));
path.lineTo((float) (2 * (Math.cos(angle) * LONG_SIZE)), 0);
canvas.drawPath(path, paint);
接下来我们需要将画出的菱形进行旋转一周,在没学习Canvas的时候你可以就直接绘制,但是这样算法设计起来就非常的凌乱,这里通过对Canvas画布进行旋转而绘制菱形的绘制方向不变,这样就实现了将菱形旋转一周的效果
for (int i = 1; i <= 6; i++)
......
canvas.rotate(60, 0, 0);
但是它的位置是在原点,我们需要将它移动到中心点的位置
canvas.translate(centerX, centerY);
最后在绘制中心圆的时候出现了问题,找不到原始画布位置了,这里需要使用canvas.save()、canvas.restore()来进行画布保存和恢复的功能,在绘制之前先保存一下画布,在绘制中心圆之前恢复一下就可以了
tips:
1、canvas.save() : 每次save都将相应的画布状态压入到栈中
2、canvas.restore() : 取出栈顶的状态
3、canvas的每次draw过程都会新建一个图层,方便之后的恢复某个状态的操作
Part 2、使用Canvas裁剪实现揭露效果
效果~
裁剪代码
canvas.clipRect(outRect);
unSelected.draw(canvas);
这代码的意思便是给画布裁剪一个矩形区域然后将图片绘制在上面,从而这样就实现了图片的裁剪功能,注意当你裁剪完之后不要忘记save和restore一下然后就是一番设计,外面是一个HorizontalScrollView通过对其滑动的监听来动态改变图片的裁剪区域,其实思路很简单但实现起来也免不了一些坑。
既然HorizontalScrollView能得到改变的值但要怎么给Drawable呢,或者说怎么给ImageView呢?这里我们使用的它里面的一个level字段,先来说一下Drawable
Drawable就是一个可画的对象,其可能是一张位图(BitmapDrawable),也可能是一个图形(ShapeDrawable),还有可能是一个图层(LayerDrawable),我们根据画图的需求,创建相应的可画对象,就可以将这个可画对象当作一块“画布(Canvas)”,在其上面操作可画对象,并最终将这种可画对象显示在画布上,有点类似于“内存画布“。
/**
* Specify the level for the drawable. This allows a drawable to vary its
* imagery based on a continuous controller, for example to show progress
* or volume level.
*
* <p>If the new level you are supplying causes the appearance of the
* Drawable to change, then it is responsible for calling
* @link #invalidateSelf in order to have itself redrawn, <em>and</em>
* true will be returned from this function.
*
* @param level The new level, from 0 (minimum) to 10000 (maximum).
*
* @return Returns true if this change in level has caused the appearance
* of the Drawable to change (hence requiring an invalidate), otherwise
* returns false.
*/
public final boolean setLevel(@IntRange(from=0,to=10000) int level)
if (mLevel != level)
mLevel = level;
return onLevelChange(level);
return false;
从源码可以看到level的范围是0-10000变化的,在这里我们定义了中间一块区域为level=5000,当level=0时为左边区域,当level=10000为右边区域,我们不妨这么假设,当图片在区域左边为5000--0,在右边的时候为10000--5000,下面给一张图来想象一下:
为HorizontalScrollView设置个OnTouch事件
int scrollX = getScrollX();//得到当前ScrollView的滑动的距离
int index_left = scrollX / icon_width;//就是上图绿色图片的索引
int index_right = index_left + 1;
for (int i = 0; i < container.getChildCount(); i++)
if (i == index_left || i == index_right) //选出左右的索引
ImageView iv_left = (ImageView) container.getChildAt(index_left);
float rate = 5000f / icon_width;//0 -- 1
iv_left.setImageLevel((int) (5000 - scrollX % icon_width * rate));//5000 -- 0
if (index_right < container.getChildCount())
ImageView iv_right = (ImageView) container.getChildAt(index_right);
iv_right.setImageLevel(
(int) (10000 - scrollX % icon_width * rate));//10000 -- 5000
else
((ImageView) container.getChildAt(i)).setImageLevel(0);//当不是左右索引的时候将其Level置为0,灰色
这触摸监听事件里面为每个ImageView设置相应的level,这样就为我们的Drawable设置了level,接下来我们需要自定义一个Drawable来控制选中图片和未选中图片的显示区域
Rect outRect = mTmpRect;
float ratio = (level / 5000f) - 1f;//左边区域0 ~ -1 右边区域1 ~ 0
Rect bounds = getBounds();//得到自身Drawable的自身区域
int w = bounds.width();
w = (int) (w * Math.abs(ratio));
int h = bounds.height();
int gravity = (ratio < 0 ? Gravity.LEFT : Gravity.RIGHT);
Gravity.apply(gravity, w, h, bounds, outRect);
canvas.save();
canvas.clipRect(outRect);
unSelected.draw(canvas);
canvas.restore();
int w = bounds.width();
w -= (int) (w * Math.abs(ratio));
int h = bounds.height();
int gravity = (ratio < 0 ? Gravity.RIGHT : Gravity.LEFT);
Gravity.apply(gravity, w, h, bounds, outRect);
canvas.save();
canvas.clipRect(outRect);
selected.draw(canvas);
canvas.restore();
tips:
1、Gravity.apply() : 对bounds区域进行裁剪,gravity可以设置裁剪的方向
就这样实现了我们想要滑动逻辑,但当你运行的时候会发现啥也看不到,what,因为我们自定义Drawable却没有为其定义边框所以才啥也看不到
@Override
protected void onBoundsChange(Rect bounds)
super.onBoundsChange(bounds);
selected.setBounds(bounds);
unSelected.setBounds(bounds);
但是还是中间区域没有显示在中间而是显示在了左边并不是我们想要的结果,我们还需要给HorizontalScrollView的容器设置一下位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
super.onLayout(changed, l, t, r, b);
icon_width = container.getChildAt(0).getWidth();
int centerX = this.getWidth() / 2;
centerX = centerX - icon_width / 2;
container.setPadding(centerX, 0, centerX, 0);
效果~
以上是关于Canvas实现Reveal揭露特效的主要内容,如果未能解决你的问题,请参考以下文章
Ruby | 产品求真,拒绝特效,像阿米尔汗的电影一样让人放心
Android UICanvas 画布 ① ( Canvas 状态栈 | Canvas 状态栈入栈与出栈 | 获取 Canvas 状态栈容量 | Canvas 状态栈原点数据 )