Android绘图之渐隐动画

实现了一个有趣的小东西:使用自定义View绘图,一边画线,画出的线条渐渐变淡,直到消失。效果如下图所示:

用属性动画或者渐变填充(Shader)可以做到一笔一笔的变化,但要想一笔渐变(手指不抬起边画边渐隐),没在Android中找到现成的API可用。所以,自己做了一个。

基本的想法是这样的:

  • 在View的onTouchEvent中记录触摸点,生成一条一条的线LineElement,放在一个List中。给每个LineElement配置一个Paint实例。
  • 在onDraw中绘制线段。
  • 变换LineElement的Paint实例的Alpha值。
  • 根据Alpha值重组线段列表

别的不说了,上代码:

package com.example.disappearinglines;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

public class DisappearingDoodleView extends View {
    final static String TAG = "DoodleView";
    class LineElement {
        static final public int ALPHA_STEP = 5;
        static final public int SUBPATH_DIMENSION = 8;
        public LineElement(){
            mPaint = new Paint();
            mPaint.setARGB(255, 255, 0, 0);
            mPaint.setAntiAlias(true);
            mPaint.setStrokeWidth(16);
            mPaint.setStrokeCap(Paint.Cap.BUTT);
            mPaint.setStyle(Paint.Style.STROKE);
        }
        public LineElement(Paint paint){
            mPaint = paint;
        }

        public void setPaint(Paint paint){
            mPaint = paint;
        }

        public void setAlpha(int alpha){
            mPaint.setAlpha(alpha);
        }

        public float mStartX = -1;
        public float mStartY = -1;
        public float mEndX = -1;
        public float mEndY = -1;
        public Paint mPaint;
    }

    private LineElement mCurrentLine = null;
    private List<LineElement> mLines = null;

    private long mElapsed = 0;
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg){
            DisappearingDoodleView.this.invalidate();
        }
    };

    public DisappearingDoodleView(Context context){
        super(context);
    }

    public DisappearingDoodleView(Context context, AttributeSet attrs){
        super(context, attrs);
    }

    @Override
    protected void onDraw(Canvas canvas){
        mElapsed = SystemClock.elapsedRealtime();
        if(mLines != null) {
            for (LineElement e : mLines) {
                if(e.mStartX < 0 || e.mEndY < 0) continue;
                canvas.drawLine(e.mStartX, e.mStartY, e.mEndX, e.mEndY, e.mPaint);
            }
            compactPaths();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        float x = event.getX();
        float y = event.getY();

        int action = event.getAction();
        if(action == MotionEvent.ACTION_UP){// end one line after finger release
            mCurrentLine.mEndX = x;
            mCurrentLine.mEndY = y;
            mCurrentLine = null;
            invalidate();
            return true;
        }

        if(action == MotionEvent.ACTION_DOWN){
            mCurrentLine = new LineElement();
            addToPaths(mCurrentLine);

            mCurrentLine.mStartX = x;
            mCurrentLine.mStartY = y;
            return true;
        }

        if(action == MotionEvent.ACTION_MOVE) {
            mCurrentLine.mEndX = x;
            mCurrentLine.mEndY = y;
            mCurrentLine = new LineElement();
            addToPaths(mCurrentLine);

            mCurrentLine.mStartX = x;
            mCurrentLine.mStartY = y;
        }

        if(mHandler.hasMessages(1)){
            mHandler.removeMessages(1);
        }
        Message msg = new Message();
        msg.what = 1;
        mHandler.sendMessageDelayed(msg, 0);

        return true;
    }

    private void addToPaths(LineElement element){
        if(mLines == null) {
            mLines = new ArrayList<LineElement>() ;
        }

        mLines.add(element);
    }

    public void compactPaths(){

        int size = mLines.size();
        int index = size - 1;
        if(size == 0) return;
        int baseAlpha = 255 - LineElement.ALPHA_STEP;
        int itselfAlpha;
        LineElement line;
        for(; index >=0 ; index--, baseAlpha -= LineElement.ALPHA_STEP){
            line = mLines.get(index);
            itselfAlpha = line.mPaint.getAlpha();
            if(itselfAlpha == 255){
                if(baseAlpha <= 0){
                    ++index;
                    break;
                }
                line.setAlpha(baseAlpha);
            }else{
                itselfAlpha -= LineElement.ALPHA_STEP;
                if(itselfAlpha <= 0){
                    ++index;
                    break;
                }
                line.setAlpha(itselfAlpha);
            }
        }

        if(index >= size){
            // all sub-path should disappear
            mLines = null;
        }
        else if(index >= 0){
            //Log.i(TAG, "compactPaths from " + index + " to " + (size - 1));
            mLines = mLines.subList(index, size);
        }else{
            // no sub-path should disappear
        }

        long interval = 40 - SystemClock.elapsedRealtime() + mElapsed;
        if(interval < 0) interval = 0;
        Message msg = new Message();
        msg.what = 1;
        mHandler.sendMessageDelayed(msg, interval);
    }
}

这个示例还可以添加一些效果,比如让线条一边变淡一边变细。

目前还有一些问题,线条粗的话,可以明显看到线段与线段之间有缝隙或裂口,哪位想到怎么优化,可以告诉我,非常感谢。^_^。

时间: 2024-12-09 01:03:48

Android绘图之渐隐动画的相关文章

第三章 Android绘图机制与处理技巧

1.屏幕尺寸信息 屏幕大小:屏幕对角线长度,单位“寸”:分辨率:手机屏幕像素点个数,例如720x1280分辨率:PPI(Pixels Per Inch):即DPI(Dots Per Inch),它是对角线的像素点数除以屏幕大小得到的:系统屏幕密度:android系统定义了几个标准的DPI值作为手机的固定DPI.(注,最后一个有笔误,正确的是1080x1920)独立像素密度(DP):android系统使用mdpi屏幕作为标准,在这个屏幕上1dp=1px,其他屏幕可以通过比例进行换算.在hdpi中,

Android绘图

Android绘图方法主要有两个步骤: (1)实现一个继承于View组件的类,并重写它的onDraw(Canavas canvas)方法; (2)显示定义的View子类,有两种方法:a.使用一个Activity来显示View子类,即 setContentView(new MyView(this, null));b.在Acitviy的布局文件中增加"包名.View子类"元素,Activiyty通过setContentView方法来使用该布局文件.下面我们来学习下Android绘制图形的三

Android绘图机制(一) View类

对android绘图机制的理解,在Android学习中可谓至关重要,包括自定义控件也是使用非常频繁的内容.最近在项目中遇到一个比较棘手的问题,项目中好几个模块都用到ListView或者GridView的”下拉刷新,上拉加载更多“功能 .一开始在网上找了大牛写的作品,用在项目中后发现时不时会出现卡壳的现象,改进以后会有所改善,不过还是感觉有所欠缺.无奈我是个处女座菜鸟,尝试着找出这些问题的根本原因却发现无从下手,所以先补补基础.( 纯文字看着确实很费劲,所以顺便引用下其他的文章)  概述 View

Android笔记二十八.Android绘图深度解析

Android绘图深度解析 转载请表明出处:http://blog.csdn.net/u012637501(嵌入式_小J的天空) Android绘图方法主要有两个步骤: (1)实现一个继承于View组件的类,并重写它的onDraw(Canavas canvas)方法; (2)显示定义的View子类,有两种方法:a.使用一个Activity来显示View子类,即 setContentView(new MyView(this, null));b.在Acitviy的布局文件中增加"包名.View子类&

Android编程之Fragment动画加载方法源码详解

上次谈到了Fragment动画加载的异常问题,今天再聊聊它的动画加载loadAnimation的实现源代码: Animation loadAnimation(Fragment fragment, int transit, boolean enter, int transitionStyle) { 接下来具体看一下里面的源码部分,我将一部分一部分的讲解,首先是: Animation animObj = fragment.onCreateAnimation(transit, enter, fragm

Android中的帧动画的简单使用

Android中动画主要有下面几种,帧动画(frame),补间动画(tween),属性动画(property) 我们平时项目中主要用的是帧动画和补间动画 帧动画需要我们准备一组静态图片,这些图片是通过分解动画得来的,静态图片连起来播放形成动画效果 我们在res目录下新建一个drawable目录,用来存放动画资源和xml文件 图片如下: girl.xml 注意android:oneshot="false|true" 该属性控制动画是否重复播放,false代表重复播放 <?xml v

Android应用开发:动画开发——XML动画

引言 当今,Android.IOS二分天下,什么Tizen.COS blabla的均为蝼蚁,一看就知道是为打发领导或为花研发资金产出的产品,根本不是为了赢得市场,为的只是博得领导一笑而已,完全可以忽视.而Android开发又因为开发语言以Java为主,入门门槛极低导致基本上是个程序员,泡两天EOE,或Android Developer Training都可以过来说"哥会开发Android app了!",那么什么才能将你的App脱颖而出呢?准确的用户痛点.良好的数据结构.简单易用的交互流

android 仿ppt进入动画效果合集

EnterAnimation android 仿ppt进入动画效果合集, 百叶窗效果,擦除效果,盒状效果,阶梯效果,菱形效果,轮子效果,劈裂效果,棋盘效果, 切入效果,扇形展开效果,十字扩展效果,随机线条效果,向内溶解效果,圆形扩展效果, 适用于各种view和viewgroup,activity即用于页面根部viewgroup, 自定义viewgroup自动换行layout, 看效果图 Series of entrance animation effects just like ppt in A

Android自定义水波纹动画Layout

Android自定义水波纹动画Layout 源码是双11的时候就写好了,但是我觉得当天发不太好,所以推迟了几天,没想到过了双11女友就变成了前女友,桑心.唉不说了,来看看代码吧. 展示效果 Hi前辈 话不多说,我们先来看看效果: 这一张是<Hi前辈>的搜索预览图,你可以在这里下载这个APP查看更多效果:http://www.wandoujia.com/apps/com.superlity.hiqianbei LSearchView 这是一个MD风格的搜索框,集成了ripple动画以及searc