2019-06-23 00:15:10 qq_16624353 阅读数 718
  • Android进阶之高级UI

    本课程采用理论、源码和实战相结合的方式来讲解Android 中 UI 高级的高级应用。课程内容涉及Activity启动流程,UI绘制的流程及原理Android画笔,画布,动画,贝塞尔曲线,滤镜等。实战带大家实现相机各种滤镜效果,和粒子爆炸效果。有问题欢迎私信我,我看到会第一时间回复。 课程大纲如下: 第一部分  原理篇 1、UI得绘制原理 2、Activity得启动流程 3、UI得绘制流程三大步骤详解。 第二部分  实用演练 1、画笔得高阶应用 2、滤镜得原理 3、滤镜的实现 4、图层混合模式 XFERMODE 5、Cavas高级应用 6、粒子爆炸效果 7、canvas实战 8、Path详解 9、Path实战 10、贝塞尔曲线 11、贝塞尔曲线实战 12、PathMeasure探索1 13、PathMeasure探索2 第三部分 实战篇 1、属性动画实战 2、仿小红书引导页平行动画1(思路分析) 3、仿小红书引导页平行动画2(代码实战) 4、仿小红书引导页平行动画3(代码实战)

    253 人正在学习 去看看 雷琪

正好在做一个和抖音差不多的APP,目前在刚启动阶段,先从实现一个抖音的点赞动画开始。。。爱心是从阿里的矢量图标库下载的一个爱心的Png图片,不是使用贝塞尔曲线画的。。。原因是我不会贝塞尔曲线(其实就是菜)我使用的是Androidx的包,如果使用的老的Support的包,请自行替换。代码是kotlin实现的,需要java的请自行翻译。最后使用方式,就是在xml文件中直接当成ConstraintLayout使用,就会自带点赞效果了。

观察一下抖音的点赞效果,实际上就是双击2下以上会触发那个点赞的动画,然后包含先从大缩小到正常大小再放大,随机的左右摇摆的动画,还有渐变的动画。所以我们只需要继承ConstrantLayout在Ontouchevent方法中实现动画就OK了。

 

 

代码如下

package tracyeminem.com.peipei.view

import android.animation.*
import android.content.Context
import android.os.SystemClock
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.animation.LinearInterpolator
import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout
import tracyeminem.com.peipei.R
import kotlin.random.Random

class LoveView(context: Context) : ConstraintLayout(context){

    var mContext : Context ?= null

    //随机爱心的旋转角度
    var num = floatArrayOf(-35f,-25f,0f,25f,35f)

    //判断是否是连续的点击事件
    private val mHits = LongArray(2)

    constructor(context: Context,attrs : AttributeSet) : this(context) {
        mContext = context
    }

    //用这个来判断是否是双击事件,判断数组中pos=1的点击事件的时间与数组中pos=0的点击事件的时间差值是否小于500,若是小于500认为是双击事件,这时需要绘制爱心图片
    override fun onTouchEvent(event: MotionEvent?): Boolean {


        System.arraycopy(mHits,1,mHits,0,mHits.size-1)
        mHits[mHits.size-1] = SystemClock.uptimeMillis()

        if(mHits[0] >= (SystemClock.uptimeMillis() - 500)){

            var iv = ImageView(context)


            //设置展示图片的大小
            var lp = LayoutParams(300,300)

            //设置图片的相对坐标是父布局的左上角开始的
            lp.leftToLeft = 0
            lp.topToTop = 0

            //设置图片相对于点击位置的坐标
            lp.leftMargin = (event?.x!! - 150F).toInt()
            lp.topMargin = (event.y!! - 230F).toInt()

            //设置图片资源
            iv.setImageDrawable(resources.getDrawable(R.drawable.heart_red))
            iv.layoutParams = lp

            //把IV添加到父布局中
            addView(iv)

            var animatorSet = AnimatorSet()
            animatorSet.play(
                scaleAni(iv,"scaleX",2f,0.9f,100,0))
                .with(scaleAni(iv,"scaleY",2f,0.9f,100,0))
                .with(rotation(iv,0,0,num[Random.nextInt(4)]))
                .with(alphaAni(iv,0F,1F,100,0))
                .with(scaleAni(iv,"scaleX",0.9f,1F,50,150))
                .with(scaleAni(iv,"scaleY",0.9f,1F,50,150))
                .with(translationY(iv,0f,-600F,800,400))
                .with(alphaAni(iv,1F,0F,300,400))
                .with(scaleAni(iv,"scaleX",1F,3f,700,400))
                .with(scaleAni(iv,"scaleY",1F,3f,700,400))


            animatorSet.start()

            animatorSet.addListener(object :AnimatorListenerAdapter(){
                override fun onAnimationEnd(animation: Animator?) {
                    super.onAnimationEnd(animation)
                    //当动画结束,把控件从父布局移除
                    removeViewInLayout(iv)
                }
            })

        }

        return super.onTouchEvent(event)
    }

    //vararg可变参数修饰符,此处可以传入多个Float类型值
    fun rotation(view:View,time:Long,delayTime:Long,vararg values:Float):ObjectAnimator{
        val ani = ObjectAnimator.ofFloat(view,"rotation",*values)
        ani.duration = time
        ani.startDelay = delayTime
        ani.interpolator = TimeInterpolator { input -> input }
        return ani
    }

    fun alphaAni(view : View,from : Float,to : Float,time: Long,delayTime: Long):ObjectAnimator{
        val ani = ObjectAnimator.ofFloat(view,"alpha",from,to)
        ani.interpolator = LinearInterpolator()
        ani.duration = time
        ani.startDelay = delayTime
        return ani
    }

    fun translationY(view: View,from: Float,to: Float,time: Long,delayTime: Long):ObjectAnimator{
        val ani = ObjectAnimator.ofFloat(view,"translationY",from,to)
        ani.interpolator = LinearInterpolator()
        ani.startDelay =  delayTime
        ani.duration = time
        return ani
    }

    fun translationX(view: View,from: Float,time: Long,to: Float,delayTime: Long):ObjectAnimator{
        val ani = ObjectAnimator.ofFloat(view,"translationX",from,to)
        ani.startDelay = delayTime
        ani.duration = time
        ani.interpolator = LinearInterpolator()
        return ani
    }

    fun scaleAni(view: View,propertyName:String,from: Float,to: Float,time: Long,delayTime: Long):ObjectAnimator{
        val ani = ObjectAnimator.ofFloat(view,propertyName,from,to)
        ani.interpolator = LinearInterpolator()
        ani.startDelay = delayTime
        ani.duration = time
        return ani
    }


}

 

2017-03-31 17:31:00 weixin_34302561 阅读数 15
  • Android进阶之高级UI

    本课程采用理论、源码和实战相结合的方式来讲解Android 中 UI 高级的高级应用。课程内容涉及Activity启动流程,UI绘制的流程及原理Android画笔,画布,动画,贝塞尔曲线,滤镜等。实战带大家实现相机各种滤镜效果,和粒子爆炸效果。有问题欢迎私信我,我看到会第一时间回复。 课程大纲如下: 第一部分  原理篇 1、UI得绘制原理 2、Activity得启动流程 3、UI得绘制流程三大步骤详解。 第二部分  实用演练 1、画笔得高阶应用 2、滤镜得原理 3、滤镜的实现 4、图层混合模式 XFERMODE 5、Cavas高级应用 6、粒子爆炸效果 7、canvas实战 8、Path详解 9、Path实战 10、贝塞尔曲线 11、贝塞尔曲线实战 12、PathMeasure探索1 13、PathMeasure探索2 第三部分 实战篇 1、属性动画实战 2、仿小红书引导页平行动画1(思路分析) 3、仿小红书引导页平行动画2(代码实战) 4、仿小红书引导页平行动画3(代码实战)

    253 人正在学习 去看看 雷琪

1,我们上一篇介绍了贝塞尔曲线推到原理和在Android里的简单使用,今天就和来写写贝塞尔曲线的实际应用,今天实现的效果图如下:

2,思路分析

  我们知道首先我们的view是一个圆,这里的圆其实是由四块三阶贝塞尔曲线组成的,左上、右上、左下、右下这四块贝塞尔曲线组成,那么让我们来开始吧

  • 准备阶段

  创建一个类MyViewCircle继承自View,重写构造方法,重写onDraw()方法

public class MyViewCircle extends View {
    public MyViewCircle(Context context) {
        this(context, null);
    }

    public MyViewCircle(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyViewCircle(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    @Override
    protected void onDraw(Canvas canvas) {

    }
}
  • 绘制X、Y轴

  由于我们的view是展示在屏幕的正中央的,为了我们以后标识点方便,这里我们以屏幕的正中心为(0,0)坐标绘制出来

  在onSizeChange()方法中获得mCenterX、mCenterY的坐标,绘制X,Y轴,代码如下:

 private int mCenterX;
 private int mCenterY;
 private Paint mPaint;
//省略代码.........................
     //初始化画笔
        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(3);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAntiAlias(true);
    
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        //初始化坐标系
        mCenterX = getWidth() / 2;
        mCenterY = getHeight() / 2;
}

    @Override
    protected void onDraw(Canvas canvas) {
     canvas.save();
        //绘制x,y轴坐标系
        canvas.drawLine(mCenterX, 0, mCenterX, getHeight(), mPaint);
        canvas.drawLine(0, mCenterY, getWidth(), mCenterY, mPaint);

        canvas.restore();
}

 运行效果如下:

 

  • 从三阶贝塞尔曲线得到半圆的效果

  我们上一篇简单的介绍了下三阶贝塞尔,当我们的两个控制点距离数据点为一下坐标时我们绘制出来的曲线是类似于四分之一圆弧的,效果图如下:

  关于怎么计算出这两个控制点相对于数据点的相对坐标的,这是一个难点,不过还好,在stackoverflow上有人计算出来了,这是链接,我们可以得出一个常量0.552284749831,即相对于原点坐标,半径是mCircleRadius我们的坐标的0.55228倍,这里我为了方便,直接取了0.5,所以这就解释了为什么我们效果图中的圆有点瘪(保持微笑)

  • 绘制数据点

  这里我们四个数据点分别是的是半径为mCircleRadius,圆心坐标为(mCenterX,mCenterY)的圆与我们X、Y轴的焦点,绘制代码如下:

    private Paint mPaintCircle;
    private Paint mPaintPoint;
    private int mCircleRadius;
    private List<PointF> mPointDatas; //放置四个数据点的集合
    private List<PointF> mPointControlls;//方式8个控制点的集合

    //省略代码.....
    //初始化数据
        mPaintCircle = new Paint();
        mPaintCircle.setColor(Color.RED);
        mPaintCircle.setStrokeWidth(10);
        mPaintCircle.setStyle(Paint.Style.STROKE);
        mPaintCircle.setAntiAlias(true);

        mPaintPoint = new Paint();
        mPaintPoint.setColor(Color.BLACK);
        mPaintPoint.setStrokeWidth(5);
        mPaintPoint.setStyle(Paint.Style.FILL);
        mPaintPoint.setAntiAlias(true);

        mCircleRadius = 150;
    
    //在onSizeChange方法中初始化四个数据点
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        //初始化坐标系
        mCenterX = getWidth() / 2;
        mCenterY = getHeight() / 2;

        mPointDatas = new ArrayList<>();
        mPointControlls = new ArrayList<>();

        mPointDatas.add(new PointF(mCenterX, mCenterY - mCircleRadius));
        mPointDatas.add(new PointF(mCenterX + mCircleRadius, mCenterY));
        mPointDatas.add(new PointF(mCenterX, mCenterY + mCircleRadius));
        mPointDatas.add(new PointF(mCenterX - mCircleRadius, mCenterY));
    }
    //在onDraw方法中绘制数据点
    @Override
    protected void onDraw(Canvas canvas) {
        //绘制x,y轴坐标系
        canvas.drawLine(mCenterX, 0, mCenterX, getHeight(), mPaint);
        canvas.drawLine(0, mCenterY, getWidth(), mCenterY, mPaint);
        //绘制数据点
        for (int i = 0; i < mPointDatas.size(); i++) {
            canvas.drawPoint(mPointDatas.get(i).x, mPointDatas.get(i).y, mPaintPoint);
        }
    }    

  效果图如下:

  • 绘制控制点

  由我们上面的的到的常量0.552284749831,这里使用的是0.5,所以,代码如下

 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        //初始化坐标系
        mCenterX = getWidth() / 2;
        mCenterY = getHeight() / 2;

        mPointDatas = new ArrayList<>();
        mPointControlls = new ArrayList<>();

        mPointDatas.add(new PointF(mCenterX, mCenterY - mCircleRadius));
        mPointDatas.add(new PointF(mCenterX + mCircleRadius, mCenterY));
        mPointDatas.add(new PointF(mCenterX, mCenterY + mCircleRadius));
        mPointDatas.add(new PointF(mCenterX - mCircleRadius, mCenterY));

        mPointControlls.add(new PointF(mCenterX + mCircleRadius / 2, mCenterY - mCircleRadius));
        mPointControlls.add(new PointF(mCenterX + mCircleRadius, mCenterY - mCircleRadius / 2));

        mPointControlls.add(new PointF(mCenterX + mCircleRadius, mCenterY + mCircleRadius / 2));
        mPointControlls.add(new PointF(mCenterX + mCircleRadius / 2, mCenterY + mCircleRadius));

        mPointControlls.add(new PointF(mCenterX - mCircleRadius / 2, mCenterY + mCircleRadius));
        mPointControlls.add(new PointF(mCenterX - mCircleRadius, mCenterY + mCircleRadius / 2));

        mPointControlls.add(new PointF(mCenterX - mCircleRadius, mCenterY - mCircleRadius / 2));
        mPointControlls.add(new PointF(mCenterX - mCircleRadius / 2, mCenterY - mCircleRadius));
    }

  //在onDraw方法中添加控制点的绘制
@Override
    protected void onDraw(Canvas canvas) {
     //绘制x,y轴坐标系
        canvas.drawLine(mCenterX, 0, mCenterX, getHeight(), mPaint);
        canvas.drawLine(0, mCenterY, getWidth(), mCenterY, mPaint);
        //绘制数据点
        canvas.save();
        for (int i = 0; i < mPointDatas.size(); i++) {
            canvas.drawPoint(mPointDatas.get(i).x, mPointDatas.get(i).y, mPaintPoint);
        }
        //绘制控制点
        for (int i = 0; i < mPointControlls.size(); i++) {
            canvas.drawPoint(mPointControlls.get(i).x, mPointControlls.get(i).y, mPaintPoint);
        }
    }

  绘制后效果图如下:

  • 绘制三阶贝塞尔曲线

  先来绘制右上角四分之一圆弧的贝塞尔曲线看看效果,在onDraw中调用如下代码:

    //利用三阶贝塞尔曲线实现画圆
        Path path = new Path();
        path.moveTo(mPointDatas.get(0).x, mPointDatas.get(0).y);
path.cubicTo(mPointControlls.get(0).x, mPointControlls.get(0).y, mPointControlls.get(1).x, mPointControlls.get(1).y, mPointDatas.get(1)x, mPointDatas.get(1).y);

    //绘制
    canvas.drawPath(path, mPaintCircle);

  看一下效果:

  看到了吧  ,效果可以吧,然后我们继续来把后面三条弧线绘制玩,代码如下:

//利用三阶贝塞尔曲线实现画圆
        Path path = new Path();
        path.moveTo(mPointDatas.get(0).x, mPointDatas.get(0).y);
        for (int i = 0; i < mPointDatas.size(); i++) {
            if (i == mPointDatas.size() - 1) {
                path.cubicTo(mPointControlls.get(2 * i).x, mPointControlls.get(2 * i).y, mPointControlls.get(2 * i + 1).x, mPointControlls.get(2 * i + 1).y, mPointDatas.get(0).x, mPointDatas.get(0).y);

            } else {
                path.cubicTo(mPointControlls.get(2 * i).x, mPointControlls.get(2 * i).y, mPointControlls.get(2 * i + 1).x, mPointControlls.get(2 * i + 1).y, mPointDatas.get(i + 1).x, mPointDatas.get(i + 1).y);
            }

        }
        canvas.drawPath(path, mPaintCircle);

  效果如下:

  • 改变数据点和控制点,重绘制view

  这一步最关键,先来看看我们下面的动画效果

   可以看到我们上面动画实现的效果移动有五个点变了:一个Y正轴上数据点改变,四个Y负轴控制点改变了,ok,知道了这些了我们基本上知道了怎么实现了,代码如下:

    private int mDuration = 1000; //动画总时间
    private int mCurrTime = 0;  //当前已进行时间
    private int mCount = 100;//将总时间划分多少块
    private float mPiece = mDuration / mCount; //每一块的时间 ;

    //在onDraw()方法中动态的刷新view,有人肯定会问,120,80之类的怎么的出来的,我只能说调出来的好嘛(手动微笑),代码如下:
    //动态改变数据点和辅助点
           mCurrTime += mPiece;
           if (mCurrTime < mDuration) {
               mPointDatas.get(0).y += 120 / mCount;
               mPointControlls.get(2).x -= 20.0 / mCount;

               mPointControlls.get(3).y -= 80.0 / mCount;
               mPointControlls.get(4).y -= 80.0 / mCount;
               mPointControlls.get(5).x += 20.0 / mCount;

               postInvalidateDelayed((long) mPiece);
           }    

  ok,这样我们基本上全部完成了,看一下效果

  ok,这样我们就实现这个效果了,有没有很简单,有需要源码的同学可以在我的Github下载,明天继续,See You Next Time!!!

2015-06-18 19:57:27 sbsujjbcy 阅读数 4490
  • Android进阶之高级UI

    本课程采用理论、源码和实战相结合的方式来讲解Android 中 UI 高级的高级应用。课程内容涉及Activity启动流程,UI绘制的流程及原理Android画笔,画布,动画,贝塞尔曲线,滤镜等。实战带大家实现相机各种滤镜效果,和粒子爆炸效果。有问题欢迎私信我,我看到会第一时间回复。 课程大纲如下: 第一部分  原理篇 1、UI得绘制原理 2、Activity得启动流程 3、UI得绘制流程三大步骤详解。 第二部分  实用演练 1、画笔得高阶应用 2、滤镜得原理 3、滤镜的实现 4、图层混合模式 XFERMODE 5、Cavas高级应用 6、粒子爆炸效果 7、canvas实战 8、Path详解 9、Path实战 10、贝塞尔曲线 11、贝塞尔曲线实战 12、PathMeasure探索1 13、PathMeasure探索2 第三部分 实战篇 1、属性动画实战 2、仿小红书引导页平行动画1(思路分析) 3、仿小红书引导页平行动画2(代码实战) 4、仿小红书引导页平行动画3(代码实战)

    253 人正在学习 去看看 雷琪

前言

文章开头来看一下本篇文章要实现的效果,如图

这里写图片描述

左边是慕课网APP中的效果,右边是58同城APP中的加载动画。

实现第一种动画

之前用图形的混合模式和贝塞尔曲线实现过慕课网的下拉刷新的加载动画。见链接慕课网app下拉刷新图标填充效果的实现,而这种动画效果在app中其实也很常见,之前的那篇文章是自定义View绘制出来的,其实这个也可以用DrawableAnimation实现,这里,我们来实现一下,看看有多简单。首先提取图片资源,图片提取自慕课网App,如图。
这里写图片描述

提取完图片之后就是编写Drawable文件

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
                android:oneshot="false">
    <item
        android:drawable="@mipmap/head_image_default"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_0"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_1"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_2"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_3"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_4"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_5"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_6"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_7"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_8"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_9"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_10"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_11"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_12"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_13"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_14"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_15"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_16"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_17"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_18"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_19"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_20"
        android:duration="50"/>

    <item
        android:drawable="@mipmap/head_image_21"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_22"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_23"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_24"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_25"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_26"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_27"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_28"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_29"
        android:duration="50"/>
    <item
        android:drawable="@mipmap/head_image_30"
        android:duration="50"/>
</animation-list>

为了复用这个控件,我们选中新建一个MoocView类继承ImageView,在构造方法中设置背景图为我们的drawable文件,设置完后拿到background强转为AnimationDrawable,通过调用AnimationDrawable对象的start方法开始动画,stop方法停止动画,有时候一开始我们的View没有显示,而当设置了View.VISIBLE后,此时动画应该立即执行,因此,我们重写setVisible方法,当设置为View.VISIBLE时开始动画,否则结束动画

package cn.edu.zafu.drawableanimation;

import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;

/**
 * User: lizhangqu(513163535@qq.com)
 * Date: 2015-06-18
 * Time: 15:57
 * 慕课网下拉刷新进度显示控件
 */
public class MoocView extends ImageView{
    private AnimationDrawable background;
    public MoocView(Context context) {
        this(context, null);
    }

    public MoocView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MoocView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    private void initView() {
        setBackgroundResource(R.drawable.refresh_anim);
        background= (AnimationDrawable) getBackground();
    }
    public void startAnimator(){
        if(background!=null){
            background.start();
        }
    }
    public void stopAnimator(){
        if(background!=null){
            background.stop();
        }
    }
    @Override
    public void setVisibility(int visibility) {
        super.setVisibility(visibility);
        if(visibility== View.VISIBLE){
            startAnimator();
        }else{
            stopAnimator();
        }
    }
}

写完了这些直接在布局文件里使用即可。

实现第二种动画

相对前面的动画,第二种动画就显得有点复杂了,第二种动画使用属性动画的相关类完成。如果要向下兼容,请使用兼容库NineOldAndroids,使用方法和元素的属性动画基本一致。

分析

在正式编码前,我们来分析一下第二种动画,首先动画可以分为四部分,圆形,正方形,三角形,底部阴影四部分,其中底部阴影动画是水平方向的缩放动画,它跟其他三个的动画同时进行,但是另外三个的动画是一个接一个执行的,并且每一个图形的动画都涉及到多种动画,比如垂直方向的平移,以及旋转。然后垂直方向从下向上运动,再从上向下运动,运动到远处的时候变换图形,而这一过程的速度变换应该是垂直方向的抛体运动,即先变小再变大。而系统并没有这个插值器,所以这个插值器也需要自己编写。那么再说另一个问题,如何实现图形的切换,其实很简单,刚开始的时候将另外两个图形设置为不可见,只显示第一个图形,监听动画,当第一个图形的动画完成后,将其隐藏,将接下来的一个图形设置为可见,同样监听这个动画,依次类推。

实现插值器

我们只要继承Interpolator接口,实现相应的方法即可。而当务之急就是找出这么一个曲线,其斜率变换是先变小再变大的,即只要构造出如图所示的曲线,当x小于0.5的时候,取曲线b,当x大于等于0.5的时候取曲线a,曲线不唯一,自己取一条即可

这里写图片描述

package cn.edu.zafu.drawableanimation;

import android.view.animation.Interpolator;

public class DecelerateAccelerateInterpolator implements Interpolator {
    private float mFactor = 1.0f;

    public DecelerateAccelerateInterpolator() {
    }

    public DecelerateAccelerateInterpolator(float factor) {
        mFactor = factor;
    }

    public float getInterpolation(float input) {
        float result;
        if (input < 0.5) {
            result = (float) (1.0f - Math.pow((1.0f - 2 * input), 2 * mFactor)) / 2;
        } else {
            result = (float) Math.pow((input - 0.5) * 2, 2 * mFactor) / 2 + 0.5f;
        }
        return result;
    }
}

编写自定义View

新建一个LoadingView 类,继承RelativeLayout

package cn.edu.zafu.drawableanimation;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.ImageView;
import android.widget.RelativeLayout;

/**
 * User: lizhangqu(513163535@qq.com)
 * Date: 2015-06-18
 * Time: 14:08
 * 58同城页面加载动画View
 */
public class LoadingView extends RelativeLayout {
    private Context mContext;
    public LoadingView(Context context) {
        this(context, null);
    }
    public LoadingView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
    }
    private int dip2px(int dip) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
    }
}

声明内部的View的相关变量

    private static final int DEFAULT_VIEW_SIZE = 28;
    private static final int DURATION = 800;
    private static final int TOP_HEIGHT=80;
    private static final int RATATION_HEIGHT=20;
    private int mViewSize;
    private ImageView mCircleView;
    private ImageView mRectView;
    private ImageView mTriangleView;
    private ImageView mBottomView;

声明动画相关的成员变量

    private AnimatorSet mAll;
    private AnimatorSet mCircleAnimator;
    private AnimatorSet mRectAnimator;
    private AnimatorSet mTriangleAnimator;

    private Animator.AnimatorListener mCircleListener;
    private Animator.AnimatorListener mRectListener;
    private Animator.AnimatorListener mTriangleListener;
    private boolean isAnimator = false;

    private final  float[] TRANSLATIONY ={0f, -dip2px(TOP_HEIGHT), 0f};
    private final float[] SCALEX = {0.9f, 0.5f, 0.2f, 0.1f, 0.05f, 0.1f, 0.2f, 0.3f, 0.5f, 0.7f, 0.9f};
    private final float[] ROTATION_RECT ={0f, 200f};
    private final float[] ROTATION_TRIANGLE = {0f, -90f};

对View进行初始化

 private void initView() {
        mViewSize = dip2px(DEFAULT_VIEW_SIZE);
        setGravity(Gravity.CENTER);

        mCircleView = new ImageView(mContext);
        mCircleView.setId(R.id.top);
        mCircleView.setBackgroundResource(R.mipmap.loading_yuan);
        LayoutParams circleParams = new LayoutParams(mViewSize, mViewSize);
        circleParams.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
        circleParams.topMargin=dip2px(TOP_HEIGHT+RATATION_HEIGHT);
        mCircleView.setLayoutParams(circleParams);
        addView(mCircleView);

        mRectView = new ImageView(mContext);
        mRectView.setPivotX(mViewSize/2);
        mRectView.setPivotY(mViewSize/2);
        mRectView.setBackgroundResource(R.mipmap.loading_fangxing);
        LayoutParams rectParams = new LayoutParams(mViewSize, mViewSize);
        rectParams.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
        rectParams.topMargin=dip2px(TOP_HEIGHT+RATATION_HEIGHT);
        mRectView.setLayoutParams(rectParams);
        addView(mRectView);

        mTriangleView = new ImageView(mContext);
        mTriangleView.setPivotY(mViewSize/2);
        mTriangleView.setPivotX(mViewSize/2);
        mTriangleView.setBackgroundResource(R.mipmap.loading_sanjiao);
        LayoutParams triangleParams = new LayoutParams(mViewSize, mViewSize);
        triangleParams.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
        triangleParams.topMargin=dip2px(TOP_HEIGHT+RATATION_HEIGHT);
        mTriangleView.setLayoutParams(triangleParams);
        addView(mTriangleView);

        mBottomView = new ImageView(mContext);

        mBottomView.setBackgroundResource(R.mipmap.loading_bottom);
        LayoutParams bottomParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        bottomParams.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
        bottomParams.addRule(RelativeLayout.BELOW,R.id.top);
        mBottomView.setLayoutParams(bottomParams);
        addView(mBottomView);

        mRectView.setVisibility(View.INVISIBLE);
        mTriangleView.setVisibility(View.INVISIBLE);

    }

初始化跟动画相关的监听器

   private void initAnimatorListener() {
        mCircleListener = new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mTriangleView.setVisibility(View.INVISIBLE);
                mCircleView.setVisibility(View.INVISIBLE);
                mRectView.setVisibility(View.VISIBLE);
            }
        };

        mRectListener = new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mCircleView.setVisibility(View.INVISIBLE);
                mTriangleView.setVisibility(View.VISIBLE);
                mRectView.setVisibility(View.INVISIBLE);
            }
        };
        mTriangleListener = new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mCircleView.setVisibility(View.VISIBLE);
                mRectView.setVisibility(View.INVISIBLE);
                mTriangleView.setVisibility(View.INVISIBLE);
                isAnimator = false;
                startAnimator();
            }
        };
    }

编写获得动画相关类的几个函数,主要是为了复用代码

    private Animator getBottomViewAnimator() {
        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mBottomView, "scaleX", SCALEX);
        objectAnimator.setInterpolator(new DecelerateAccelerateInterpolator());
        return objectAnimator;
    }

    private Animator getTranslationAnimator(Object object) {
        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(object, "translationY", TRANSLATIONY);
        objectAnimator.setInterpolator(new DecelerateAccelerateInterpolator());
        return objectAnimator;
    }

    private Animator getRotationAnimator(Object object,float[] values) {
        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(object, "rotation", values);
        objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        return objectAnimator;
    }

编写开始动画的函数,其逻辑前面已经介绍了,关键是三个图形的动画有一个延时,而底部的动画与三个图形的动画一起进行,如果当前正在进行动画,则直接return,在动画执行前记得设置监听器。

    public void startAnimator() {
        if(isAnimator){
            return;
        }
        isAnimator = true;

        mCircleAnimator = new AnimatorSet();
        mCircleAnimator.setDuration(DURATION);
        mCircleAnimator.playTogether(getTranslationAnimator(mCircleView), getBottomViewAnimator());
        mCircleAnimator.addListener(mCircleListener);


        mRectAnimator = new AnimatorSet();
        mRectAnimator.setDuration(DURATION);
        mRectAnimator.setStartDelay(DURATION);
        mRectAnimator.playTogether(getTranslationAnimator(mRectView), getBottomViewAnimator(), getRotationAnimator(mRectView, ROTATION_RECT));
        mRectAnimator.addListener(mRectListener);

        mTriangleAnimator = new AnimatorSet();
        mTriangleAnimator.setDuration(DURATION);
        mTriangleAnimator.setStartDelay(DURATION * 2);
        mTriangleAnimator.playTogether(getTranslationAnimator(mTriangleView), getBottomViewAnimator(), getRotationAnimator(mTriangleView, ROTATION_TRIANGLE));
        mTriangleAnimator.addListener(mTriangleListener);

        mCircleAnimator.start();
        mRectAnimator.start();
        mTriangleAnimator.start();

    }

既然有开始动画也就有结束动画的函数,结束动画有个缓冲过程,即执行完当前正在执行的动画后才结束,该函数需要将各个动画停止,让后将监听器移除,并设置成员变量isAnimator为false

   public void stopAnimator() {
        if (mCircleAnimator != null) {
            mCircleAnimator.end();
            mCircleAnimator.removeAllListeners();
            mCircleAnimator = null;

        }
        if (mRectAnimator != null) {
            mRectAnimator.end();
            mRectAnimator.removeAllListeners();
            mRectAnimator = null;
        }
        if (mTriangleAnimator != null) {
            mTriangleAnimator.end();
            mTriangleAnimator.removeAllListeners();
            mTriangleAnimator = null;
        }
        isAnimator = false;

    }

重新setVisible方法,正如之前所说的,图形可能刚开始时不可见的,后来可以通过setVisible方法使其可见,可见的同时动画也应该开始执行,这时候重写setVisible方法实现相应的逻辑即可

   @Override
      public void setVisibility(int visibility) {
        super.setVisibility(visibility);
        if (visibility == View.VISIBLE) {
            if (!isAnimator) {
                startAnimator();
            }
        } else {
            if (isAnimator) {
                stopAnimator();
            }

        }
    }

题外话

关于属性动画可以参见郭霖的三篇文章,个人觉得讲得很详细。

ViewWrapper wrapper = new ViewWrapper(mButton);
ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();

源码下载

2016-11-19 19:18:54 luulmm520 阅读数 909
  • Android进阶之高级UI

    本课程采用理论、源码和实战相结合的方式来讲解Android 中 UI 高级的高级应用。课程内容涉及Activity启动流程,UI绘制的流程及原理Android画笔,画布,动画,贝塞尔曲线,滤镜等。实战带大家实现相机各种滤镜效果,和粒子爆炸效果。有问题欢迎私信我,我看到会第一时间回复。 课程大纲如下: 第一部分  原理篇 1、UI得绘制原理 2、Activity得启动流程 3、UI得绘制流程三大步骤详解。 第二部分  实用演练 1、画笔得高阶应用 2、滤镜得原理 3、滤镜的实现 4、图层混合模式 XFERMODE 5、Cavas高级应用 6、粒子爆炸效果 7、canvas实战 8、Path详解 9、Path实战 10、贝塞尔曲线 11、贝塞尔曲线实战 12、PathMeasure探索1 13、PathMeasure探索2 第三部分 实战篇 1、属性动画实战 2、仿小红书引导页平行动画1(思路分析) 3、仿小红书引导页平行动画2(代码实战) 4、仿小红书引导页平行动画3(代码实战)

    253 人正在学习 去看看 雷琪

Path Data Markup Syntax 

下面来解释一下“M 100,240 C510,300 80,100 300,160 H40 v80”这样字符串的意义。 
分为四种情况来解释: 
1. 移动指令:Move Command(M):M 起始点  或者:m 起始点 
比如:M 100,240或m 100,240 
使用大写M时,表示绝对值; 使用小写m时; 表示相对于前一点的值,如果前一点没有指定,则使用(0,0)。

2. 绘制指令(Draw Command): 
我们可以绘制以下形状: 
(1) 直线:Line(L) 
(2) 水平直线: Horizontal line(H) 
(3) 垂直直线: Vertical line(V) 
(4) 三次方程式贝塞尔曲线: Cubic Bezier curve(C) 
(5) 二次方程式贝塞尔曲线: Quadratic Bezier curve(Q) 
(6) 平滑三次方程式贝塞尔曲线: Smooth cubic Bezier curve(S) 
(7) 平滑二次方程式贝塞尔曲线: smooth quadratic Bezier curve(T) 
(8) 椭圆圆弧: elliptical Arc(A) 
上面每种形状后用括号括起的英文字母为命令简写的大写形式,但你也可以使用小写。使用大写与小写的区别是:大写是绝对值,小写是相对值。 
比如:L 100, 200 L 300,400表示从绝对坐标点(100,200)到另一绝对坐标点(300,400)的一条直线。而l 100, 200 l 300,400则表示相对上一点(如果未指定,则默认为(0,0)坐标点)开始计算的坐标点(100,200)到坐标点为(300,400)的一条直线。 
当我们重复使用同一种类型时,就可以省略前面的命令。比如:L 100, 200 L 300,400简写为:L 100, 200 300,400。


SVG是一种矢量图格式,是Scalable Vector Graphics三个单词的首字母缩写。在xml文件中的标签是<vector>,画出的图形可以像一般的图片资源使用,例子如下:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportHeight="24.0"
    android:viewportWidth="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z" />
</vector>

其所画出的图形为   

于此同时,android studio提供了丰富的图片资源,可以右键module,new->vector asset选择,如下:

是不是很羡慕这些酷酷的图形,当然自己也可以去动手去做。显而易见,上面例子的重点是在PathData里面的那一大窜数字:

android:pathData="M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z" />

 

xml文件中: 

android:viewportHeight="24.0" android:viewportWidth="24.0"> 是声明的画布大小。

先慢慢学习一些基本的语法:

  • M:move to 移动绘制点,作用相当于把画笔落在哪一点。
  • L:line to 直线,就是一条直线,注意,只是直线,直线是没有宽度的,所以你什么也看不到。
  • Z:close 闭合,嗯,就是把图封闭起来。
  • C:cubic bezier 三次贝塞尔曲线
  • Q:quatratic bezier 二次贝塞尔曲线
  • A:ellipse 圆弧

每个命令都有大小写形式,大写代表后面的参数是绝对坐标,小写表示相对坐标,相对于上一个点的位置。参数之间用空格或逗号隔开。

命令详解:

  • M (x y) 把画笔移动到x,y,要准备在这个地方画图了。
  • L (x y) 直线连到x,y,还有简化命令H(x) 水平连接、V(y)垂直连接。仅仅是连起来,并不会像笔画线一样显示图形。
  • Z,没有参数,连接起点和终点
  • C(x1 y1 x2 y2 x y),控制点(x1,y1)( x2,y2),终点x,y 。
  • Q(x1 y1 x y),控制点(x1,y1),终点x,y
  • C和Q会在下文做简单对比。
  • A(rx ry x-axis-rotation large-arc-flag sweep-flag x y) 
  • android:pathData=" M50,50 a10,10 1,1 0 1,0" />
    rx ry 椭圆半径 
    x-axis-rotation x轴旋转角度 
    large-arc-flag 为0时表示取小弧度,1时取大弧度 (舍取的时候,是要长的还是短的)
    sweep-flag 0取逆时针方向,1取顺时针方向 

L的用法:

android:pathData=" M10,0 L10,40 40,40" /> 
把画笔放在(10,0)位置,连线10,40点 在连线40,40点。。。于是,一个直角三角形出来了~这里没有写z,没什么关系。
    

 Q和C的对比: 

Q
 android:pathData="M0,0 q30,90 80,20"/>   

 
控制点1,30,90 : 
控制点2,80,20  : 

C

android:pathData=" M0,0 c0,0 30,90 80,20" />
C 第一控制点(0,0) 第二控制点(30,90) 结束点(80,20) 或 c 第一控制点 第二控制点 结束点

现在修改第一个控制点:

android:pathData=" M0,0 c50,0 30,90 80,20" />

a:

这么多 数字,怎么看啊,可以直接拉到下面看作用。

android:pathData=" M50,50 a10,5 0,1 0 1,0" />
以50,50为起点,逆时针画
椭圆图形,x轴半径10,y轴半径5 

 

转动x轴~~~

android:pathData=" M50,50 a10,5 90,1 0 1,0" />



我想要椭圆上半段,此处修改为x轴半径的两倍
android:pathData=" M50,50 a10,5 90,1 0 20,0" />
椭圆左半段
android:pathData=" M50,50 a10,5 90 1 0 0 10" />
椭圆右半段
android:pathData=" M50,50 a10,5 90 1 1 0 10" />
<path
    android:fillColor="#fff70000"  下
       android:pathData=" M50,50 a10,5 0 1 0 1 0" />
    <path
        android:fillColor="#FFF22420" 上
        android:pathData=" M50,50 a10,5 0 1 1 1 0" />
    <path
        android:fillColor="#fff57000"右
        android:pathData=" M50,50 a10,5 0 1 1 1 1" />
    <path
        android:fillColor="#FF323243"左
        android:pathData=" M50,50 a10,5 0 1 0 0 1" />

出现上面的情况可以想到是因为,起始点50,50在椭圆中的位置不同。那么,再修改一下。

    android:pathData=" M50,50 a10,5 0 1 1 0 7" />  修改了右边椭圆的代码
  现在取的是大弧度,所以看到这样的效果,如果 7改为10(也就是y轴半径的两倍)这刚好会在 一半的位置。

现在取小弧度看看,

android:pathData=" M50,50 a10,5 0 0 1 0 7" /> ,可以看到小弧度 顺时针画图。

再修改为逆时针,
android:pathData=" M50,50 a10,5 0 0 0 0 7" /> 

椭圆的属性 差不多讲解完成了,如下

android:pathData=" M50,50 a10,5 0 0 0 0 7" />

10,5 为椭圆x,y轴半径

第一个0 为 x轴旋转角度

第二个0 为取大小弧度,0为小,1为大

第三个0 为顺逆时针,0为逆1为顺

第四个 7 为修改修改起始点在椭圆中的位置,该位置为起始点。

这是前辈留下的图:



以上内容部分来源于 网络。本文不已盈利为目的,仅供大家学习参考。



2016-11-18 13:48:48 sw950729 阅读数 988
  • Android进阶之高级UI

    本课程采用理论、源码和实战相结合的方式来讲解Android 中 UI 高级的高级应用。课程内容涉及Activity启动流程,UI绘制的流程及原理Android画笔,画布,动画,贝塞尔曲线,滤镜等。实战带大家实现相机各种滤镜效果,和粒子爆炸效果。有问题欢迎私信我,我看到会第一时间回复。 课程大纲如下: 第一部分  原理篇 1、UI得绘制原理 2、Activity得启动流程 3、UI得绘制流程三大步骤详解。 第二部分  实用演练 1、画笔得高阶应用 2、滤镜得原理 3、滤镜的实现 4、图层混合模式 XFERMODE 5、Cavas高级应用 6、粒子爆炸效果 7、canvas实战 8、Path详解 9、Path实战 10、贝塞尔曲线 11、贝塞尔曲线实战 12、PathMeasure探索1 13、PathMeasure探索2 第三部分 实战篇 1、属性动画实战 2、仿小红书引导页平行动画1(思路分析) 3、仿小红书引导页平行动画2(代码实战) 4、仿小红书引导页平行动画3(代码实战)

    253 人正在学习 去看看 雷琪

之前一直想要个加载的loading。却不知道用什么好,然后就想到了太极图标,最后效果是有了,不过感觉用来做loading简直丑到爆!!!
实现效果很简单,我们不要用什么贝塞尔曲线啥的,因为太极无非就是圆圆圆,只要画圆就ok了。来上代码:
因为有黑有白,所以定义2个画笔分别为黑和白。

 private void inital() {
        whitePaint = new Paint();
        whitePaint.setAntiAlias(true);
        whitePaint.setColor(Color.WHITE);
        blackPaint = new Paint();
        blackPaint.setAntiAlias(true);
        blackPaint.setColor(Color.BLACK);
    }

最后来画3个圆就可以解决了:

  protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Point centerPoint = new Point(width / 2, height / 2);
        canvas.translate(centerPoint.x, centerPoint.y);
        canvas.rotate(angle);
        //绘制两个半圆
        int radius = Math.min(bitmapwidth, bitmapheight) / 2;
        RectF rect = new RectF(-radius, -radius, radius, radius);   //绘制区域
        canvas.drawArc(rect, 90, 180, true, blackPaint);            //绘制黑色半圆
        canvas.drawArc(rect, -90, 180, true, whitePaint);           //绘制白色半圆
        //绘制两个小圆
        int smallRadius = radius / 2;
        canvas.drawCircle(0, -smallRadius, smallRadius, blackPaint);
        canvas.drawCircle(0, smallRadius, smallRadius, whitePaint);
        //绘制鱼眼
        canvas.drawCircle(0, -smallRadius, smallRadius / 4, whitePaint);
        canvas.drawCircle(0, smallRadius, smallRadius / 4, blackPaint);
        if (load) {
            angle += 10;
            if (angle >= 360) {
                angle = 0;
            }
        }
        invalidate();
    }

是不是很简单,也就几行代码就解决了,一开始我还打算用贝塞尔曲线的(疯了!!)。

没有更多推荐了,返回首页