问题重述
这次的题目有点长,特意的将两个类似的东西进行了划分。也是为了完全重现我在遇到和解决这两个问题时候的过程。在一开始,是需求那边要我做一个我们聊天面板的渐变效果。经过多方查证,终于实现了这个功能。后来在迭代版本的时候又要加一个观众列表的右侧透明渐变效果,就想到了还用公屏同样的代码,然而中间竟然遇到了坑,还找不到攻略(好慌-。-),所以记录一下,防止后面遇到相同的问题没有资源参考。
-
先上个效果图,静态滴
你们要的 Git地址,拿去不谢!(●´∀`●)
聊天面板的实现
在开始我就是要实现一个聊天面板的消息渐变消失效果(如上图),经过查找资料,最后使用了下面的代码实现:
// 实现渐变效果
Paint mPaint;
private int layerId;
private LinearGradient linearGradient;
public void doTopGradualEffect(){
mPaint = new Paint();
// dst_in 模式,实现底层透明度随上层透明度进行同步显示
//(即上层为透明时,下层就透明,并不是上层覆盖下层)
// 具体关于PorterDuff.Mode的东西大家可以自行查阅了解
final Xfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
mPaint.setXfermode(xfermode);
// 透明位置不变,位于Recyclerview偏上位置
// 使用 CLAMP 模式边缘拉伸,完美契合背景颜色
linearGradient = new LinearGradient(0.0f, 0.0f, 0.0f, 100.0f, new int[]{0, Color.BLACK}, null, Shader.TileMode.CLAMP);
addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(canvas, parent, state);
mPaint.setXfermode(xfermode);
mPaint.setShader(linearGradient);
canvas.drawRect(0.0f, 0.0f, parent.getRight(), 200.0f, mPaint);
mPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
// 此处 Paint的参数这里传的null, 在传入 mPaint 时会出现
// 第一次打开黑屏闪现的问题
// 注意 saveLayer 不能省也不能移动到onDrawOver方法里
layerId = c.saveLayer(0.0f, 0.0f, (float) parent.getWidth(), (float) parent.getHeight(), null, Canvas.ALL_SAVE_FLAG);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
// 该方法作用自行百度
super.getItemOffsets(outRect, view, parent, state);
}
});
}
注释还是比较清楚的。主要使用了 Recyclerview 的 addItemDecoration 和 PorterDuff.Mode 的 DST_IN 模式,这个模式下绘制的效果会受到源图像(即代码中的 drawRect)透明度的影响,因为我们使用了渐变的 Shader--LinearGradient,所以画出来的矩形透明度是渐变的,当我们 restore 的时候就会影响到底层的透明度(即公屏消息的透明度)。
观众列表右侧透明渐变
这里我要强调 右侧,因为我在考虑这个功能时马上想到了使用上面类似的代码,然而在左侧是OK的,可怎么也移动不到右侧。开始走了一条不归路。因为左侧是OK的,所以我想只需要把透明位置移动到右侧就可以了,就想修改 drawRect 的左右边界线来实现移动,但是都说了是不归路(╥╯^╰╥),期间进行了各种修改调试,最后甚至尝试了盖一张图片,添加一个毛玻璃蒙层,但效果都不好。在休息了一晚后,我又回到上次的记录点(左侧OK),终于让我发现了问题所在,原来控制透明度的位置不是通过控制矩形的位置,而是通过 linearGradient 的位置来实现的。后面又出现一些小问题,最终出世了以下代码:
// 实现渐变效果
Paint mPaint;
private int layerId;
private LinearGradient linearGradient;
private int preWidth = 0;// Recyclerview宽度动态变化时,监听每一次的宽度
public void doTopGradualEffect(){
mPaint = new Paint();
// dst_in 模式,实现底层透明度随上层透明度进行同步显示(即上层为透明时,下层就透明,并不是上层覆盖下层)
final Xfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
mPaint.setXfermode(xfermode);
addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(canvas, parent, state);
// 当linearGradient为空即第一次绘制 或 Recyclerview宽度发生改变时,
// 重新计算透明位置
if (linearGradient == null || preWidth!=parent.getWidth()){
// 透明位置从最后一个 itemView 的一半处到 Recyclerview 的最右边
linearGradient = new LinearGradient(parent.getWidth()-(itemViewWidth/2),
0.0f, parent.getWidth(), 0.0f, new int[]{Color.BLACK, 0}, null, Shader.TileMode.CLAMP);
preWidth = parent.getWidth();
}
mPaint.setXfermode(xfermode);
mPaint.setShader(linearGradient);
canvas.drawRect(0.0f, 0.0f, parent.getRight(), parent.getBottom(), mPaint);
mPaint.setXfermode(null);
canvas.restoreToCount(layerId);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
// 此处 Paint的参数这里传的null, 在传入 mPaint 时会出现第一次打开黑屏闪现的问题
// 注意 saveLayer 不能省也不能移动到onDrawOver方法里
layerId = c.saveLayer(0.0f, 0.0f, (float) parent.getWidth(), (float) parent.getHeight(), null, Canvas.ALL_SAVE_FLAG);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
// 该方法作用自行百度
super.getItemOffsets(outRect, view, parent, state);
}
});
}
苦恼了一天的问题原来就在于一个参数的修改 (〜^㉨^)〜,终于顺利完成了这个功能,后来由于我们的需求是少于3个人右对齐,3个人以上左对齐,所以添加了动态计算的代码。
- Tip:我示例的代码都是在我自定义的Recyclerview中,所以是直接调用的addItemDecoration,还有记得 saveLayer 时如果传了 Paint 的话可能出现界面打开的时候闪一个黑屏。
转载请联系作者--维权骑士,盗版必究