滑块滑动&滑块点击&good、best、miss效果实现
实现思路大致是,设置滑块(继承自button)对象,两个轨道每个轨道都对应一个ArrayList<EachButton>滑块列表,每个滑块都定义了它的轨道位置、滑动动画、起始时间、滑动时间、滑动路径的属性。
游戏页面得到来自模式选择页面的会话,传递过来了音频信息、模式信息,然后调用节奏点获取的HandleData类来获取音频的节奏时间点N个,初始化N个滑块,将‘节奏时间点-滑动时间’作为滑块的滑动动画延迟时间(这样滑到按钮处时就刚好到那个乐点了),将模式信息中的速度、滑块多少等来量化滑块移动时间、节奏点个数的信息,然后开始全部滑块的start方法来开启动画。
点击下方的FREE按钮会触发方法,得到该按钮所在轨道的最近一个滑块的位置,与按钮的位置作比较给出good、best级别,将该滑块移出轨道队列;如果滑块动画结束仍没被点击(还在队列),则判断为miss,并移出队列。
滑块移动动画核心代码为:
Path path=new Path();
path.moveTo(fromX,fromY);//设置path开始坐标
path.lineTo(toX,toY);//设置path结束坐标
animator=ObjectAnimator.ofFloat(this,
Property.of(EachButton.class,Float.class,"translationX"),
Property.of(EachButton.class,Float.class,"translationY"),
path);//设置animator是沿着path路径这种方式移动的
animator.setDuration(time);//设置动画时长,即整个移动过程的时间
animator.setStartDelay(startTime);//设置动画延迟时间,到节奏点了再让滑块动
LinearInterpolator lin = new LinearInterpolator();//匀速运动
animator.setInterpolator(lin);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
mlayout.addView(eachButton);
}
@Override
public void onAnimationEnd(Animator animation) {
mlayout.removeView(eachButton);
}
});
good、best、miss提示的弹出、淡化、消失的伪跳跃渐变效果的核心代码为:
View view = LayoutInflater.from(mcontext).inflate(R.layout.toast, null);//得到视图里的toast,更新toast的文本为miss
TextView textView = view.findViewById(R.id.textView);
textView.setText("miss");
Toast toast = GameActivity.toast;//(设置一个toast变量,有初始样式,每次点击后更新它的TextView,并show()出来即可)
toast.setView(view);//更新toast的样式
toast.setDuration(Toast.LENGTH_SHORT);
ObjectAnimator.ofArgb(textView, "textColor",
Color.parseColor("#ff333333"),
Color.parseColor("#00333333"))
.setDuration(1000)
.start();//设置toast的动画显示时间及颜色变化(灰色到灰色透明这种伪渐变效果)
toast.show();//展示这个toast
底部按钮点击时变色的核心代码为:
ObjectAnimator.ofArgb(bottoms[i], "backgroundColor",
Color.parseColor("#F3E77C"),
Color.parseColor("#F8E097"))
.setDuration(500)
.start();
toast.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/toast"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView"
android:text=""
android:textStyle="bold"
android:textSize="40sp"
android:shadowRadius="7.0"
android:shadowDx="-7"
android:shadowDy="7"
android:shadowColor="@color/colorSilver"
android:layout_marginBottom="125dp"
android:layout_gravity="center_horizontal"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
></TextView><!--这里是constraintlayout,与底部左边的距离依靠具体手机分辨率决定-->
</androidx.constraintlayout.widget.ConstraintLayout>
EachButton类
package com.example.free.Classes;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Path;
import android.util.Property;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.widget.AppCompatButton;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.example.free.GameActivity;
import com.example.free.R;
import com.example.free.ResultActivity;
import java.util.Timer;
import java.util.TimerTask;
public class EachButton extends AppCompatButton {
//这里的坐标位置都是用的像素px,因为实时获得滑块的运动位置得到返回值是像素
int fromX=0;//滑块移动的起始点x坐标
int fromY=0;//起始点y坐标
int toX=0;//滑块移动的终止点x坐标
int toY=0;//终止点y坐标
int startTime=0;//开始移动的时间点
int time=0;//滑动过程的时间长度
int width=240;//px 滑块宽
int height=120;//滑块长
ConstraintLayout mlayout;//滑块所在的视图
Context mcontext;//滑块所在的上下文活动
public ObjectAnimator animator;//动画实例
public boolean state=true;//按钮的状态,是否还存在于队列中
int path=0;//所在轨道
public EachButton(Context context,int _width,int _height,int fx,int fy,int tox,int toy,int st,int t,int p){
super(context);
this.fromX=fx;
this.fromY=fy;
this.toX=tox;
this.toY=toy;
this.startTime=st;
this.time=t;
this.path=p;
width=_width;
height=_height;
mcontext=context;
this.setBackgroundColor(0xaaF8E097);
this.setWidth(width);
this.setHeight(height);
this.layout(1000,0,1000+width,height);//layout里的参数是滑块初始位置的四个顶点的坐标(左上x,左上y,右下x,右下y)
}
//通过设置滑块动画的延迟时间,比如20s时是一个节奏点,我们从一开始就初始化了滑块的动画,设置延迟时间为20s
public void start(ConstraintLayout layout){
mlayout=layout;
Path path=new Path();
path.moveTo(fromX,fromY);//设置path开始坐标
path.lineTo(toX,toY);//设置path结束坐标
animator=ObjectAnimator.ofFloat(this,
Property.of(EachButton.class,Float.class,"translationX"),
Property.of(EachButton.class,Float.class,"translationY"),
path);//设置animator是沿着path路径这种方式移动的
animator.setDuration(time);//设置动画时长,即整个移动过程的时间
animator.setStartDelay(startTime);//设置动画延迟时间,到节奏点了再让滑块动
LinearInterpolator lin = new LinearInterpolator();//匀速运动
animator.setInterpolator(lin);
final EachButton eachButton=this;//设置滑块动画监听动作,等到动画结束后"销毁"该滑块,方便进行下一次点击计算,否则点击动作识别的一直是第一个滑块
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
mlayout.addView(eachButton);//
}
@Override
public void onAnimationEnd(Animator animation) {
mlayout.removeView(eachButton);//动画结束移除该滑块
//滑块的消除(移出buttons队列)由两种行为触发,一种是点击,一种是没点到过去了的miss状态,以下是第二种
if(eachButton.state) {//eachButton还在队列里,但动画结束了,这意味着没点击成功
View view = LayoutInflater.from(mcontext).inflate(R.layout.toast, null);//得到视图里的toast,更新toast的文本为miss
TextView textView = view.findViewById(R.id.textView);
textView.setText("miss");
Toast toast = GameActivity.toast;
toast.setView(view);//更新toast的样式
toast.setDuration(Toast.LENGTH_SHORT);
ObjectAnimator.ofArgb(textView, "textColor",
Color.parseColor("#ff333333"),
Color.parseColor("#00333333"))
.setDuration(1000)
.start();//设置toast的动画显示时间及颜色变化(灰色到灰色透明这种伪渐变效果)
toast.show();//展示这个toast
GameActivity.buttons.get(eachButton.path).remove(0);//将button移出队列
GameActivity.missTimes+=1;//miss次数加一
//如果最后一个滑块是由miss结尾的,则在这里结束队列并且跳转至成绩结算页面
if((GameActivity.buttons.get(0).size()+GameActivity.buttons.get(1).size()==0)&&GameActivity.activityState) {
final Intent iintent = new Intent(mcontext, ResultActivity.class);
iintent.putExtra("wholeScore",GameActivity.wholeScore);
iintent.putExtra("resultScore",GameActivity.resultScore);
iintent.putExtra("bestTimes",GameActivity.bestTimes);
iintent.putExtra("goodTimes",GameActivity.goodTimes);
iintent.putExtra("missTimes",GameActivity.missTimes);
TimerTask task = new TimerTask(){
public void run(){
mcontext.startActivity(iintent);
}
};
Timer timer = new Timer();
timer.schedule(task, 2000);
}
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.start();//开始动画
}
}