IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    超酷的计步器APP(一)——炫酷功能实现,自定义水波纹特效、自定义炫酷开始按钮、属性动画的综合体验

    summer发表于 2016-10-31 04:28:10
    love 0

    超酷的计步器APP(一)——炫酷功能实现,自定义水波纹特效、自定义炫酷开始按钮、属性动画的综合体验

    好久没写博客了,没给大家分享技术了,真是有些惭愧。这段时间我在找工作,今年Android的行情也不怎么好,再加上我又是一个应届生,所以呢,更是不好找了。但是我没有放弃,经过自己的不懈努力,还是找到了自己喜欢的Android工作,心里的一块石头终于落下了。但是迎接我来的是更多的挑战,我喜欢那种不断的挑战自我,在困难中让自己变得更强大的感觉。相信阳光总在风雨后,因为每一个你不满意的现在,都有一个你没有努力的曾经。所以我们一起努力,为了生活,生下来、活下去。


    今天我们来一起学习一个很酷的小项目——计步器。
    关于这个项目我想分两篇博客来和大家分享,一篇先来学习界面和功能中用到的自定义控件,下篇来实现动画和计步器连接起来开始计算步数的效果
    我们先来看效果图。

    这里写图片描述

    是不是很酷很炫呢,哈哈,我觉得酷酷哒。
    那么我们今天先来学习那个很酷的效果图的实现。
    点击开始按钮,出现结束和暂停按钮,开始按钮隐藏,横线变波浪的效果

    我们先新建一个Android项目,开始在main_activity.xml中书写布局
    先来看这个布局的Component Tree
    这里写图片描述
    我们来写他的布局

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/bg4_dark_blackish_green"
    >

    <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/activity_walking_backgroud"
    android:orientation="vertical">

    <include layout="@layout/top_bar" />

    <TextView
    android:id="@+id/step_count"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:layout_marginTop="90dp"
    android:textColor="@color/white"
    android:textSize="70sp" />

    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:layout_marginTop="18dp"
    android:text="计算步数"
    android:textColor="@color/white"
    android:textSize="13sp" />

    <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginBottom="35dp"
    android:layout_marginLeft="30dp"
    android:layout_marginRight="50dp"
    android:layout_marginTop="60dp"
    android:orientation="horizontal"
    android:weightSum="2">

    <RelativeLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_weight="1">

    <ImageView
    android:id="@+id/iv_calories"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentLeft="true"
    android:src="@mipmap/calories" />

    <TextView
    android:id="@+id/calories"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentRight="true"
    android:layout_toRightOf="@id/iv_calories"
    android:textColor="@color/white"
    android:textSize="24sp" />

    <TextView
    android:id="@+id/tv_calories"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@id/calories"
    android:layout_toRightOf="@id/iv_calories"
    android:text="热量:千卡"
    android:textColor="@color/white"
    android:textSize="10sp" />
    </RelativeLayout>

    <RelativeLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_weight="1">

    <ImageView
    android:id="@+id/iv_time"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:src="@mipmap/time" />

    <TextView
    android:id="@+id/time"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentRight="true"
    android:layout_toRightOf="@id/iv_time"
    android:text="0"
    android:textColor="@color/white"
    android:textSize="24sp" />

    <TextView
    android:id="@+id/tv_time"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@id/time"
    android:layout_toRightOf="@id/iv_time"
    android:text="时间:分钟"
    android:textColor="@color/white"
    android:textSize="10sp" />
    </RelativeLayout>

    </LinearLayout>
    </LinearLayout>

    <com.adu.running.view.WaveView
    android:id="@+id/wave_view"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:layout_marginTop="-20dp"
    />
    <RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_marginLeft="30dp"
    android:layout_marginRight="30dp"
    android:layout_marginTop="27dp">

    <com.adu.running.view.CircleButton
    android:id="@+id/stop"
    android:layout_width="94dp"
    android:layout_height="94dp"
    android:layout_alignParentLeft="true"
    android:layout_centerVertical="true"
    android:layout_gravity="center"
    android:text="结束"
    android:textColor="@color/white"
    android:visibility="gone"/>

    <com.adu.running.view.CircleWaveButton
    android:id="@+id/start"
    android:layout_width="94dp"
    android:layout_height="94dp"
    android:layout_centerInParent="true"
    android:textColor="@color/white"/>

    <com.adu.running.view.CircleButton
    android:id="@+id/bt_continue"
    android:layout_width="94dp"
    android:layout_height="94dp"
    android:layout_alignParentRight="true"
    android:layout_centerVertical="true"
    android:layout_gravity="center"
    android:text="暂停"
    android:textColor="@color/white"
    android:visibility="gone"/>
    </RelativeLayout>

    </LinearLayout>

    如果你直接复制上面的代码到你的项目中肯定会报错的,因为上面的布局中我们引用了一个自定义的toolbar布局和几个颜色值,还有几个自定义View
    一会我会把代码给大家传上去,或者直接看我GitHub上面的源码

    我们现在看下面的三个自定义View怎么来完成的

    WaveView.java自定义波纹类

    package com.adu.running.view;

    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Paint;
    import android.graphics.Path;
    import android.os.Handler;
    import android.os.Message;
    import android.util.AttributeSet;
    import android.view.View;

    /**
    * Created by adu on 2016/10/30.
    */

    public class WaveView extends View {

    //波纹颜色
    private int waveColor = 0xff0099CC;
    // 振幅
    private float swing = 0;
    private int height;
    private int width;
    private int ms = 30;
    private float isPause=0f;
    private boolean isRun = false;
    //绘制波纹的画笔
    private Paint wavePaint;

    //Path类可以预先在View上将N个点连成一条"路径"
    // 然后调用Canvas的drawPath(path,paint)即可沿着路径绘制图形
    private Path path1;
    private Path path2;
    private Path path3;

    public WaveView(Context context) {
    super(context);
    init();
    }

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

    public WaveView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
    }

    private void init() {
    // 初始绘制波纹的画笔
    wavePaint = new Paint();
    // 去除画笔锯齿
    wavePaint.setAntiAlias(true);
    //设置画笔颜色
    wavePaint.setColor(waveColor);
    //设置线宽
    wavePaint.setStrokeWidth(5);
    //设置风格为空心
    wavePaint.setStyle(Paint.Style.STROKE);
    path1 = new Path();
    path2 = new Path();
    path3 = new Path();
    }

    /**
    * 计算view高度宽度大小
    * @param widthMeasureSpec
    * @param heightMeasureSpec
    */

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    initLayoutParams();
    }
    private void initLayoutParams() {
    height = this.getHeight();
    width = this.getWidth();
    }

    @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    setPath();
    wavePaint.setStrokeWidth(6);
    wavePaint.setAlpha(100);//设置透明度
    canvas.drawPath(path1, wavePaint);

    wavePaint.setStrokeWidth(3);
    wavePaint.setAlpha(80);
    canvas.drawPath(path2, wavePaint);

    wavePaint.setAlpha(60);
    canvas.drawPath(path3, wavePaint);
    }

    /**
    * 设置三个线条上下震动的振幅
    */

    private void setPath() {
    int x = 0;
    int y = 0;
    path1.reset();//清除掉path里的线条和曲线
    for (int i = 0; i < width; i++) {
    x = i;
    y = (int) (isPause*40* Math.sin(i * 2*0.7f * Math.PI / width+swing) + height*0.5);
    if (i == 0) {
    path1.moveTo(x, y);//定位绘画开始位置
    }
    //绘制圆滑曲线,即贝塞尔曲线;(x, y)为控制点,(x + 1, y)为结束点
    path1.quadTo(x, y, x + 1, y);
    }
    path2.reset();
    for (int i = 0; i < width; i++) {
    x = i;
    y = (int) (isPause*40* Math.sin(i * 2*0.7f * Math.PI / width+swing+0.3f) + height*0.5);
    if (i == 0) {
    path2.moveTo(x, y);
    }
    path2.quadTo(x, y, x + 1, y);
    }
    path3.reset();
    for (int i = 0; i < width; i++) {
    x = i;
    y = (int) (isPause*40* Math.sin(i * 2*0.7f * Math.PI / width+swing+0.3f) + height*0.5);
    if (i == 0) {
    path3.moveTo(x, y);
    }
    path3.quadTo(x, y, x + 1, y);
    }

    path1.close();//回到初始点形成封闭的曲线
    path2.close();
    path3.close();
    }

    public void start(){
    this.isRun=true;
    this.isPause=1.0f;
    new MyThread().start();//让波纹在子线程中运行
    }
    public void stop(){
    this.isRun=false;
    this.isPause=0.0f;
    invalidate();//请求重新绘制的界面
    }

    private class MyThread extends Thread {
    @Override
    public void run() {
    while (isRun) {
    swing+=-0.25f;
    mHandler.sendEmptyMessage(1);
    try {
    Thread.sleep(ms);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }
    private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
    if (msg.what == 1) {
    invalidate();//请求重新绘制的界面
    }
    }
    };
    }

    让我们在MainActivity的布局中新建两个Button按钮开对自定义波纹类进行测试,分别调用它的start( )和stop( )方法,运行结果如下:
    这里写图片描述
    点击开始,波纹会上下起伏,点击停止,它会恢复到停止状态
    接着,我们看下面的三个按钮是怎么实现的,其实是两个自定义的Button

    CircleWaveButton.java 开始按钮

    package com.adu.running.view;

    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Paint;
    import android.graphics.RadialGradient;
    import android.graphics.Shader;
    import android.os.Handler;
    import android.os.Message;
    import android.util.AttributeSet;
    import android.widget.Button;
    import com.adu.running.R;

    /**
    * Created by adu on 2016/10/30.
    */

    public class CircleWaveButton extends Button{

    //这是默认颜色
    private int paintColor= R.color.circle_bule_bbd4e7;
    //画圆的画笔
    private Paint paint = new Paint();
    //画字的画笔
    private Paint textPaint = new Paint();
    private int radiusInt = 0;
    //这是默认文字内容
    private String text="开始";
    private Boolean isStart = false;

    private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
    super.handleMessage(msg);
    invalidate();
    if (isStart) {
    radiusInt++;
    if (radiusInt > 50) {
    radiusInt = 0;
    }
    sendEmptyMessageDelayed(0, 20);
    }
    }
    };
    public CircleWaveButton(Context context) {
    super(context);
    init();
    }

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

    public CircleWaveButton(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
    }

    private void init() {
    setBackgroundColor(getResources().getColor(R.color.running));
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    int centre = getWidth() / 2;
    int radius = centre;
    //绘制圆的画笔
    Paint newPaint = new Paint();
    newPaint.setAntiAlias(true); // 去除画笔锯齿
    newPaint.setStyle(Paint.Style.FILL);// 设置风格为实线

    //用来进行环形渲染
    Shader shader = new RadialGradient(centre, centre, radius, getResources().getColor(paintColor), getResources().getColor(R.color.running), Shader.TileMode.CLAMP);
    //设置图像效果,使用Shader可以绘制出各种渐变效果
    newPaint.setShader(shader);
    //绘制圆形(圆心的x坐标,圆心的y坐标,圆的半径,绘制时所使用的画笔)
    canvas.drawCircle(centre, centre, radius, newPaint);

    paint.setColor(getResources().getColor(paintColor));
    paint.setStyle(Paint.Style.FILL); //设置风格为实线
    paint.setAntiAlias(true); // 去除画笔锯齿

    for(int i=1;i<=5;i++){
    paint.setAlpha(20*i);//设置绘制图形的透明度
    //绘制圆形(圆心的x坐标,圆心的y坐标,圆的半径,绘制时所使用的画笔)
    canvas.drawCircle(centre, centre, radius * (10- i)/ 10, paint);
    }

    paint.setColor(getResources().getColor(paintColor));
    paint.setStyle(Paint.Style.STROKE); //设置风格为空心
    paint.setStrokeWidth(radius * 2 / 12); //设置线宽

    for(int i=0;i<3;i++) {
    paint.setAlpha(60-i*20);//设置绘制图形的透明度
    //绘制圆形(圆心的x坐标,圆心的y坐标,圆的半径,绘制时所使用的画笔)
    canvas.drawCircle(centre, centre, radius * (14-i*2 + radiusInt / 50.0f) / 16, paint);
    }

    textPaint.setTextSize(getTextSize());
    textPaint.setAntiAlias(true);
    textPaint.setStyle(Paint.Style.FILL);
    textPaint.setColor(getTextColors().getDefaultColor());

    //获取文字的宽度值
    float length = textPaint.measureText(text);
    //绘制文字
    canvas.drawText(text, centre - length / 2, centre + getTextSize() / 3, textPaint);
    }
    public void start() {
    isStart = true;
    handler.sendEmptyMessage(0);
    }

    public void stop() {
    isStart = false;
    handler.removeMessages(0);
    }
    }

    我们在MainActivity设置CircleWaveButton的点击事件调用它的start( )方法,会看到下面的效果
    这里写图片描述
    这里写图片描述

    下面我们看最后一个自定义Button,结束的Button和暂停的Button只是颜色不一样,其他都是一样的。

    CircleButton.xml暂停结束按钮

    package com.adu.running.view;

    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Paint;
    import android.util.AttributeSet;
    import android.widget.Button;
    import com.adu.running.R;

    /**
    * Created by adu on 2016/10/30.
    */

    public class CircleButton extends Button {
    //画圆
    private Paint paint = new Paint();
    //画字
    private Paint textPaint = new Paint();

    private int color;

    public CircleButton(Context context) {
    super(context);
    init();
    }
    public CircleButton(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
    }
    public CircleButton(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
    }

    private void init() {
    setBackgroundColor(getResources().getColor(R.color.running));
    color=getResources().getColor(R.color.running);
    }

    @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    int centre = getWidth() / 2;
    int radius = centre;

    paint.setColor(color);
    paint.setStyle(Paint.Style.FILL);
    paint.setAntiAlias(true);
    //绘制圆形(圆心的x坐标,圆心的y坐标,圆的半径,绘制时所使用的画笔)
    canvas.drawCircle(centre, centre, radius, paint);

    textPaint.setTextSize(getTextSize());
    textPaint.setAntiAlias(true);
    textPaint.setStyle(Paint.Style.FILL);
    textPaint.setColor(getTextColors().getDefaultColor());

    float length = textPaint.measureText(getText().toString());
    //绘制文字
    canvas.drawText(getText().toString(), centre - length / 2, centre + getTextSize() / 3, textPaint);
    }
    //改变文字颜色
    public void setPaintColor(int color) {
    this.color = getResources().getColor(color);
    }
    }

    我们就不在测试这个Button了,直接写开始的那个效果
    点击开始按钮,然后隐藏它,一个动画的效果出现结束和暂停按钮,并且开启波纹,点暂停波纹停止,点击结束退出。
    我们之间在MainActivity中写代码

    package com.adu.running;

    import android.animation.Animator;
    import android.animation.AnimatorSet;
    import android.animation.ObjectAnimator;
    import android.content.Context;
    import android.graphics.Point;
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.view.Display;
    import android.view.View;
    import android.view.WindowManager;
    import android.view.animation.DecelerateInterpolator;
    import butterknife.BindView;
    import butterknife.ButterKnife;
    import butterknife.OnClick;
    import com.adu.running.view.CircleButton;
    import com.adu.running.view.CircleWaveButton;
    import com.adu.running.view.WaveView;

    public class MainActivity extends AppCompatActivity {

    @BindView(R.id.wave_view) WaveView waveView;
    @BindView(R.id.startButton) CircleWaveButton startButton; //开始
    @BindView(R.id.stop) CircleButton stopButton; //停止
    @BindView(R.id.bt_continue) CircleButton btContinue; //暂停继续

    private boolean isPause = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);
    initView();
    }

    private void initView() {

    btContinue.setPaintColor(R.color.text_color_1e78be);
    startButton.setPaintColor(R.color.circle_bule_bbd4e7);
    stopButton.setPaintColor(R.color.circle_red_cd3a33);
    startButton.start();

    }

    @OnClick({ R.id.stop, R.id.startButton, R.id.bt_continue })
    public void onClick(View view) {
    switch (view.getId()) {
    case R.id.stop: //停止并退出
    System.exit(0);
    break;
    case R.id.startButton: //开始
    startAnimation();
    waveView.start();
    break;
    case R.id.bt_continue: //暂停继续

    if (isPause){
    btContinue.setText("暂停");
    isPause = false;
    waveView.start();
    }else {
    btContinue.setText("继续");
    isPause = true;
    waveView.stop();
    }
    break;
    }
    }

    /**
    * 按钮动画效果实现的方法
    */

    private void startAnimation() {
    btContinue.setVisibility(View.VISIBLE);//显示暂停按钮
    stopButton.setVisibility(View.VISIBLE);//显示停止按钮
    //获取屏幕的大小,并把屏幕的宽也就是x赋值给width
    WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();
    Point size = new Point();
    display.getSize(size);
    int width = size.x;

    //属性动画,传入"alpha"参数,将开始按钮从不透明改变为透明
    Animator animatorStart = ObjectAnimator.ofFloat(startButton, "alpha", 1.0f, 0f);
    AnimatorSet animatorSetStart = new AnimatorSet();//组合动画
    animatorSetStart.playTogether(animatorStart);
    animatorSetStart.setInterpolator(new DecelerateInterpolator());//动画效果设置为减速
    animatorSetStart.setDuration(1000);//设置动画时长

    //将暂停按钮由透明改变为不透明
    Animator animatorContinue1 = ObjectAnimator.ofFloat(btContinue, "alpha", 0f, 1.0f);
    animatorContinue1.setDuration(3000);//设置动画时长
    //将暂停按钮从中间向右边移动
    Animator animatorContinue2 = ObjectAnimator.ofFloat(btContinue, "translationX", -width / 3, btContinue.getX());
    animatorContinue2.setDuration(2000);//设置动画时长
    AnimatorSet animatorSetContinue = new AnimatorSet();
    animatorSetContinue.playTogether(animatorContinue1, animatorContinue2);// 并行
    animatorSetContinue.setInterpolator(new DecelerateInterpolator());//动画效果设置为减速

    //将结束按钮由透明改变为不透明
    Animator animatorStop1 = ObjectAnimator.ofFloat(stopButton, "alpha", 0f, 1.0f);
    animatorStop1.setDuration(3000);//设置动画时长
    //将结束按钮从中间向左边移动
    Animator animatorStop2 = ObjectAnimator.ofFloat(stopButton, "translationX", width / 3, 0);
    animatorStop2.setDuration(2000);//设置动画时长
    AnimatorSet animatorSetStop = new AnimatorSet();
    animatorSetStop.playTogether(animatorStop1, animatorStop2);// 并行
    animatorSetStop.setInterpolator(new DecelerateInterpolator());//动画效果设置为减速

    //启动三个按钮动画
    animatorSetStart.start();
    animatorSetStop.start();
    animatorSetContinue.start();

    startButton.setVisibility(View.GONE);
    }
    }

    我们运行代码,看看效果
    这里写图片描述

    我们就简单的完成了一个这样的动画效果,看起来还是蛮酷的嘛。


    这篇博客,我们主要学习了三个自定义View的实现,然后组合在一起形成一个很酷的效果。
    下篇博客,我开始给大家讲这个动画和计步器连接起来开始计算步数的效果

    我把这篇博客的Demo上传到这里,如果想直接看完整的动画和计步器的代码,请大家看我的GitHub,如果喜欢的话可以star一下,谢谢大家的支持

    点击下载Demo



沪ICP备19023445号-2号
友情链接