续二,用RecyclerView来实现苹果后台样式的卡片布局

       继续上次的DEMO。不知道有没有人留意到那个扭曲的菜单呢?这就是Web OS中很有趣的小彩虹菜单。不知道有没有人想过这样的菜单如何实现呢?其实,菜单主要分两部分,一个是每个菜单item的位置布局变化和背景图片变化。首先说说菜单背景吧!背景有两个特点,第一是图片场景模糊,第二是图片扭曲。先说,图片模糊如何处理吧。

private Bitmap getBitmap(int width, int height){//获取背景图片Bitmap
        Bitmap bitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(bitmap);
        Paint paint = new Paint();
        paint.setColor(Color.parseColor("#86000000"));
        paint.setStyle(Paint.Style.FILL);
        BlurMaskFilter filter = new BlurMaskFilter(30f,BlurMaskFilter.Blur.NORMAL);
        paint.setMaskFilter(filter);
        Rect rect = new Rect(-30,0,width+30,height);
        c.drawRect(rect,paint);
        return bitmap;
    }

其中,设置画笔属性Paint.SetMaskFilter就可以设置模糊滤镜。一张带有模糊效果的图片就诞生了。在这里我还另外一种模糊特效,就是毛玻璃效果:

    if (VERSION.SDK_INT > 16) {
        Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);

        final RenderScript rs = RenderScript.create(context);
        final Allocation input = Allocation.createFromBitmap(rs, sentBitmap, Allocation.MipmapControl.MIPMAP_NONE,
                Allocation.USAGE_SCRIPT);
        final Allocation output = Allocation.createTyped(rs, input.getType());
        final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
        script.setRadius(radius /* e.g. 3.f */);
        script.setInput(input);
        script.forEach(output);
        output.copyTo(bitmap);
        return bitmap;
    }
对于这种图片特效还有非常之多,我就不展开分析了。

说完图片模糊效果就该说说图片扭曲效果了。图形扭曲很常见,比如哈哈镜,就是一种扭曲成像。汽车的后视镜,也是一种图片扭曲效果。再比如我们用的全景照片的在浏览也是。全景照片怎么说都是一张线性图片,如何把它立体化呈现在我们眼中呢?其实就是应用了一个凸透镜效果,把原有的线性图片组织成一个圆展示出来的。是一个图片扭曲的经典案例。废话不多说,图片扭曲主要引用了一个方法:

Canvas.drawBitmapMash(Bitmap:bit,Width:int,Height:int,MeshVerts:int[],VertOffset:int,Colors:int[],ColorOffset:int,paint:Paint);这个方法不仅可以实现图像位置扭曲还可以实现颜色扭曲。现在说一个图片扭曲的原理吧。




如上两图,现在正常的Bitmap进行网格编号,标出每个点的实际坐标数据,用一个数组保存起来,然后对所有的点的坐标进行扭曲运算,比如图二。扭曲后的坐标点发生了变化,绘制出来的bitmap也随之发生变化。实现视图扭曲的效果。至于颜色扭曲同理可得。

       不知道有没有人留意到,“小彩虹菜单”并非单一的扭曲,它是中间肥厚,两边细的一个扭曲,而且随波浪形。没错,这是一个复合的余弦函数波合成图。假设余弦函数f(X) = a * Cos(b* (x+c)+d)+e;

设最高峰函数为f1(x),最低峰函数为f2(x)。因为f1(x),f2(x)
图像的最高峰在同一个坐标上可以得出,(b1*(x+c1)+d1) = (b2*(x+c2)+d2);假设b1 = b2,c1 = c2,d1 = d2

可得,最大值max = (a1+e1)-(a2+e2);最小值min = (-a1+e1)-(-a2+e2)

最后方程组解方程,我就不详细解答了。

说了这么多,上代码:

public class MeshBitmapDrawable extends Drawable{

    private MeshHelper helper;
    private Bitmap bitmap;
    private Paint paint;

    public MeshBitmapDrawable(@NonNull Bitmap bitmap, @NonNull MeshHelper helper){
        this.bitmap = bitmap;
        this.helper = helper;
        paint = new Paint();
        paint.setAntiAlias(true);
    }

    public MeshHelper getMeshHelper(){
        return helper;
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.save();
        canvas.translate(helper.getTanslationX(),helper.gettTanslationY());
        canvas.drawBitmapMesh(bitmap,helper.getMeshWidth(),helper.getMeshHeight(),helper.getMeshVerts(),helper.getVertOffset(),helper.getColors(),helper.getColorOffset(),paint);
        canvas.restore();
    }

    @Override
    public void setAlpha(int alpha) {
        paint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        paint.setColorFilter(colorFilter);
    }

    @Override
    public int getOpacity() {
        return paint.getAlpha();
    }

    public interface MeshHelper{

        public int getMeshWidth();

        public int getMeshHeight();

        public float[] getMeshVerts();

        public void warp(float ...values);

        public int getVertOffset();

        public void restore();

        public int[] getColors();

        public int getColorOffset();

        public float getTanslationX();

        public float gettTanslationY();

        public void setTanslation(float tanslationX,float tanslationY);

        public float getTackPositionY(float x,float positionX,int row,float smalltall);

    }
}

public class SimpleMeshHelper implements MeshBitmapDrawable.MeshHelper{

    private final int MESHWIDTH = 50;
    private final int MESHHEIGHT = 10;
    private float minGap = 40;
    private float maxGap = 80;
    private final int COUNT = (MESHWIDTH+1) * (MESHHEIGHT+1);
    private float[] verts = new float[COUNT * 2];
    private float[] orig = new float[COUNT * 2];
    private float tanslationX;
    private float tanslationY;

    private int bitmapWidth = 1;

    public SimpleMeshHelper(int bitmapWidth,int bitmapHeight){
        if(bitmapHeight > 0)
            this.bitmapWidth = bitmapWidth;
        int index = 0;
        for(int i = 0;i <= MESHHEIGHT;i++){//初始化坐标数组
            float fy = bitmapHeight * i / MESHHEIGHT;
            for(int j = 0;j <= MESHWIDTH;j++){
                float fx = bitmapWidth * j / MESHWIDTH;
                orig[index * 2 + 0] = verts[index * 2 + 0] = fx;
                orig[index * 2 + 1] = verts[index * 2 + 1] = fy;
                index ++;
            }
        }
    }

    public void setGap(float maxGap,float minGap){//设置两条曲线的最大和最小差
        this.maxGap = maxGap;
        this.minGap = minGap;
    }

    @Override
    public int getColorOffset() {
        return 0;
    }

    @Override
    public int getMeshWidth() {
        return MESHWIDTH;
    }

    @Override
    public int getMeshHeight() {
        return MESHHEIGHT;
    }

    @Override
    public float[] getMeshVerts() {
        return verts;
    }

    @Override
    public void restore() {
        System.arraycopy(orig,0,verts,0,verts.length);
    }

    @Override
    public void warp(float ...values) {//变形
        if(values == null || values.length <= 0) return;
        float x = values[0];//最高点X坐标
        float t = values.length > 1 ? values[1] : minGap;//下面线条的最高值
        int index = 0;
        for(int i = 0;i <= MESHHEIGHT;i++){//运算变化后的坐标
            for(int j = 0;j <= MESHWIDTH;j++){
                verts[index * 2 + 1] = calculate(x,orig[index * 2 + 0],i,t);
                index ++;
            }
        }
    }

    /*
     *正弦波运算
     * @params offsetX   X轴偏移量
     * @params x         实时最高点的X坐标
     * @params row       网格层数
     * @params t         最下面曲线的高度最大值
     * @return           返回对应网格Y坐标
     */
    private float calculate(float offsetX,float x,int row,float t){
        row = row >= MESHHEIGHT ? MESHHEIGHT : row;
        float minT = t >= minGap ? minGap : t;
        float syncCap = (minGap-minT)/minGap*(maxGap-minGap)+minGap;
        float a = ((maxGap-syncCap+2*minT)/2-minT)/MESHHEIGHT*(MESHHEIGHT-row)+minT;
        float b = ((maxGap+syncCap)/2) /MESHHEIGHT*(MESHHEIGHT-row);
        float result = (maxGap+minT) - ((a * (float)Math.cos((Math.PI/bitmapWidth)*(x-offsetX)))+b);
        return result;
    }

    @Override
    public float getTackPositionY(float x, float positionX, int row, float smalltall) {//获取轨道数据
        return calculate(x,positionX,row,smalltall);
    }

    @Override
    public int getVertOffset() {
        return 0;
    }

    @Override
    public int[] getColors() {
        return null;
    }

    @Override
    public void setTanslation(float x, float y) {//设置绘画偏移量
        tanslationX = x;
        tanslationY = y;
    }

    @Override
    public float gettTanslationY() {
        return tanslationY;
    }

    @Override
    public float getTanslationX() {
        return tanslationX;
    }
}

在这里,我并没有在Drawable中写死逻辑,承接上文所述,拔插效果。给扩展留下更大的空间,也便于维护和重用。
其实,“小彩虹菜单”的主要东西就到这里了。至于item菜单的布局排列,可以实现的方法可以很多种,但一定都走不出轨迹的运算。轨迹运算弄明白了,这也不是问题了。
    在这里强调一下绘画优化问题,在“小彩虹”菜单中,绘制并不复杂。如果遇到类似复杂的动态UI的绘制,优化的必要性就非常强了。关于绘制优化主要有几点
一,绘制运算能用int类型的就不用float类型,能用float类型的就不用double类型
二,运算函数能用API中的方法就不要自己写,因为api中的方法的运算是通过底层C实现的,效率会比java高,内存     占用也小,特别是繁杂的运算。
三,如果运算复杂度过大,绘制特耗时的可以选择用异步处理,首先考虑SurfaceView和GLSurfaceView。
四,绘制刷新尽可能使用局部绘制,这样会大大缩减绘制事件,减少开销。
五,在设置控件背景的时候慎重考虑,可以不设的话尽量不设。
在android的绘制机制中,没绘制一次的单元事件是16ms,如果超出这个时间,界面就可能会卡,失帧。所以在界面绘制优化的时候可以考虑调试一下耗时,逐渐调整至16ms以内。
    在这里,我想说一个观点。有些人一开始就想很多优化的地方开始折腾,但我觉得,一起这样不如把效果弄出来了再去细细讨论如何优化的问题。如果一开始就想得很完美,做起来就很纠结,缚手缚脚,非常被动。



后续:http://blog.csdn.net/rj113/article/details/66585474

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值