Android自定义绘制:Shader - 模仿iOS滑动解锁

来源:互联网 发布:程序员电脑壁纸1080p 编辑:程序博客网 时间:2024/06/06 03:18


     拖动进度条的时候,文字上有一坨类似光照的东西,闪闪的飘过去,类似 iOS 系统的 “滑动来解锁”,  通过这篇文章介绍的 Shader ,可以很轻松的实现这种效果~ 


 一、Shader 是什么


     Shader 是辅助 Paint 的一个工具,它本身不具有绘制任何内容的能力。给 Paint 上设置了一个 Shader 以后, 用这个 Paint 绘制的任何东西,显示出来的 颜色 都是由 Shader 中获取的,绘出的 图形 不受影响。 (绘制 Bitmap 不受 Shader 影响)

注: 对自定义 VIew 和绘制这部分还不清楚的童鞋, 可以看一下官网的这篇教程 http://developer.android.com/training/custom-views/index.html 。


二、Shader 的作用
     Shader 就好像一大块完整的图像,这个图像可以是渐变的颜色, 也可以是一个 Bitmap 。
     这块图像上原先盖了一个板子, 把Shader 挡住,当你用设置了 Shader 的 Paint 绘制内容的时候,就像是在这个板子上挖洞, 你可以挖方的,圆的,扇形的,或者文字形状的都可以, 挖了洞以后, 就能透过这些洞看到你设置的 Shader 上的内容了。

     比如下面的这些例子
     LinearGradient    


     RadialGradient


     SweepGradient


     当然, 效果最明显的还是 BitmapShader ,就像下面这样, 透过文字看妹子 ~ ~
     BitmapShader


     加上个颜色混合一下,  立马就是女鬼的感觉。。
     ComposeShader  (这里为了突出效果,合成 Mode 用的是 PorterDuff.Mode.MULTIPLY)


     通过上面的例子, 应该能明白 图像(Shader) 和 板子上挖出来的洞(Paint绘制的内容) 的比喻。


三、实例

     如果上面的内容都理解了,回到文章开头的那个例子


分析一下, 这个效果需要几块内容:
1、一个 LinearGradient , 两头是文字的颜色, 中间透明, 这样从 “滑动来解锁” 这个 “洞” 看过去的时候, 就好像是文字上有光照
2、通过 Paint 绘制文字 “滑动来解锁”, 就是在盖着 Shader 的板子上挖洞
3、控制 LinearGradient , 让它可以横向移动, 这样就能看到 “光” 在文字上移动的效果了


下面就是一步一步把需要的东西用代码实现出来了
     首先,创建 Shader 比较简单, 但是绘制文字, 还要考虑一大堆奇奇怪怪的问题,还是直接继承系统 TextView 比较好。
     另外,Shader 需要能够左右移动, 这个就需要通过 Matrix 来实现了,Matrix 相关的问题比较复杂,这里先不展开, 大概就是通过 Matrix 提供的一批接口,经过它内部的一系列屌炸天的数学运算,可以让画面上的东西发生变化, 包括左右平移, 知道这个就够了。
     最后,为了能让 Shader 跟随进度条移动, View 内部需要有一个 progress 标志当前移动到了哪里。

好, 这些都做完以后, 这个类看起来应该是这个样子:

public class ShimmerTextView extends TextView {    public final static int PROGRESS_COUNT = 1000;    private Matrix mGradientMatrix = null;    private float progress = 0;    private float mShimmerStep = 0;    public ShimmerTextView(Context context) {        super(context);    }    public ShimmerTextView(Context context, AttributeSet attrs) {        super(context, attrs);    }    public ShimmerTextView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    public void setProgress(float progress) {        //进度更新了以后,通过 invalidate 通知 View 更新自己的显示内容        this.progress = progress;        invalidate();     }    @Override    protected void onFinishInflate() {        super.onFinishInflate();        // View 加载以后, 创建一个 Matrix 实例, 后面就通过对它的变化来影响 LinearGradient        mGradientMatrix = new Matrix();    }}


     现在需要创建一个 LinearGradient , 由于一开始文字上应该是没有光的, 后面滑动 progress 的过程中光才出现, 那么创建LinearGradient 时, 需要给他指定一个 负数的起始点,到0截止。
     另外,考虑到适配的问题,光的宽度要根据 View 自动调整, 所以在 onSizeChanged 里面创建这个 LinearGradient,并设置合适的起始位置, Shader 的颜色从 TextView 上设置的文字颜色获取, 这样在 xml 中修改 textColor 时 Shader 就会直接使用文字的颜色,看起来是文字的颜色, 其实是透过挖出来的洞看到了 跟文字颜色相同的 Shader 。
     最后, 根据 progress ,平移 Shader 的 Matrix , 让它根据 progress 计算出 对应的位置,显示出来,就好了~~ 

public class ShimmerTextView extends TextView {    public final static int PROGRESS_COUNT = 1000;    private Matrix mGradientMatrix = null;    private float progress = 0;    private float mShimmerStep = 0;    public ShimmerTextView(Context context) {        super(context);    }    public ShimmerTextView(Context context, AttributeSet attrs) {        super(context, attrs);    }    public ShimmerTextView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    public void setProgress(float progress) {        //进度更新了以后,通过 invalidate 通知 View 更新自己的显示内容        this.progress = progress;        invalidate();     }    @Override    protected void onFinishInflate() {        super.onFinishInflate();        // View 加载以后, 创建一个 Matrix 实例, 后面就通过对它的变化来影响 LinearGradient        mGradientMatrix = new Matrix();    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        // 动态设置 闪光条的宽度        int shaderWidth = w / 3;         //起始位置为负,让闪光默认不现实出来        LinearGradient shader = new LinearGradient(-shaderWidth, 0, 0, 0,               // 两头为文字的颜色, 中间 透明, 模拟光照在文字上                new int[]{getCurrentTextColor(), Color.TRANSPARENT, getCurrentTextColor()},                new float[]{0, 0.5f, 1},                Shader.TileMode.CLAMP);        getPaint().setShader(shader);        // mShimmerStep 用于表示 每一个进度代表要移动多少的距离, 因为 Shader 默认要藏起来, 所以移动距离要多一个Shader 的宽度        mShimmerStep = (1.0f * w + shaderWidth) / PROGRESS_COUNT;    }    @Override    protected void onDraw(Canvas canvas) {        mGradientMatrix.setTranslate(progress * mShimmerStep, 0);        getPaint().getShader().setLocalMatrix(mGradientMatrix);        super.onDraw(canvas);    }}



布局 和 Activity 都很简单, 直接贴上来~

activity_shimmer.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <org.sjun.myapplication.widget.ShimmerTextView        android:id="@+id/shimmer"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:gravity="center"        android:padding="10dp"        android:text="滑动来解锁"        android:textColor="@android:color/black"        android:textSize="50sp"        android:textStyle="bold" />    <SeekBar        android:id="@+id/seek"        android:layout_width="match_parent"        android:layout_height="wrap_content" /></LinearLayout>


ShimmerActivity.java
public class ShimmerActivity extends FragmentActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_shimmer);        final ShimmerTextView shimmerTextView = (ShimmerTextView) findViewById(R.id.shimmer);        SeekBar seekBar = (SeekBar) findViewById(R.id.seek);        seekBar.setMax(ShimmerTextView.PROGRESS_COUNT);        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {            @Override            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {                shimmerTextView.setProgress(progress);            }            @Override            public void onStartTrackingTouch(SeekBar seekBar) {            }            @Override            public void onStopTrackingTouch(SeekBar seekBar) {            }        });    }}


这样就 o 了, 拖动 SeekBar 就能看到 文字上有闪闪的一条在移动的效果了, 为了保持简单明了,我这是通过 SeekBar 手动控制 Shader 位置, 相信熟悉 PropertyAnimation 的小伙伴, 很容易就可以把这个例子改造成可以自动播放的动画形式,这个不属于本文讨论的重点, 我这里就不展开了~




0 0
原创粉丝点击