最近要做个小功能,现在市面上面的直播平台,像虎牙、斗鱼、抖音和快手等等,两个主播PK的时候进度条上面会有一个动画一直在重复,挺好看的,直接上图。
pk
简单说下思路,因为ProgressBar已经基本够使用了,所以就懒得重写个自定义ProgressBar,因为现在做的是分开的两个view,到时候想替换的话,会方便很多。
整体思路是ProgressBar因为是均分的进度条,会根据始末位置算百分比,我们同样可以根据进度条移动的距离,算出控件需要对应移动的距离。举个栗子:
进度条的最小单位 / 进度条的范围 = 移动的最小距离 / 总的距离(也就是控件的总宽度)
下面开始上代码:这个是播放gif写的自定义view
package com.cgg.pkprogress;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Movie;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
public class GifView extends View {
//gif图片资源ID
protected int progress_gif;
protected int progress_width;
//gif动态效果总时长,在未设置时长时默认为1秒
private static final int DEFAULT_MOVIE_DURATION = 1000;
//Movie实例,用来显示gift图片
private Movie mMovie;
//显示gift图片的动态效果的开始时间
private long mMovieStart;
//动态图当前显示第几帧
private int mCurrentAnimationTime = 0;
//图片离屏幕左边的距离
private float mLeft;
//图片离屏幕上边的距离
private float mTop;
//图片的缩放比例
private float mScale;
//图片在屏幕上显示的宽度
private int mMeasuredMovieWidth;
//图片在屏幕上显示的高度
private int mMeasuredMovieHeight;
//是否显示动画,为true表示显示,false表示不显示
private boolean mVisible = true;
//动画效果是否被暂停
private volatile boolean mPaused = false;
public GifView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GifView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
obtainStyledAttributes(attrs);
}
private void obtainStyledAttributes(AttributeSet attrs) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
// 获取自定义属性
final TypedArray attributes = getContext().obtainStyledAttributes(attrs, R.styleable.GifProgressBar);
progress_gif = attributes.getResourceId(R.styleable.GifProgressBar_progress_gif, -1);
progress_width = (int) attributes.getDimension(R.styleable.GifProgressBar_progress_width, 10);
attributes.recycle();
if (progress_gif != -1) {
mMovie = Movie.decodeStream(getResources().openRawResource(
progress_gif));
}
}
/**
* 设置gif图资源
*
* @param giftResId
*/
public void setMovieResource(int giftResId) {
progress_gif = giftResId;
mMovie = Movie.decodeStream(getResources().openRawResource(
progress_gif));
requestLayout();
}
/**
* 手动设置 Movie对象
*
* @param movie Movie
*/
public void setMovie(Movie movie) {
this.mMovie = movie;
requestLayout();
}
/**
* 得到Movie对象
*
* @return Movie
*/
public Movie getMovie() {
return mMovie;
}
/**
* 设置要显示第几帧动画
*
* @param time
*/
public void setMovieTime(int time) {
mCurrentAnimationTime = time;
invalidate();
}
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mMovie != null) {
int movieWidth = mMovie.width();
int movieHeight = mMovie.height();
int maximumWidth = MeasureSpec.getSize(widthMeasureSpec);
float scaleW = (float) movieWidth / (float) maximumWidth;
mScale = 1f / scaleW;
mMeasuredMovieWidth = maximumWidth;
mMeasuredMovieHeight = (int) (movieHeight * mScale);
setMeasuredDimension(mMeasuredMovieWidth, mMeasuredMovieHeight);
} else {
setMeasuredDimension(getSuggestedMinimumWidth(),
getSuggestedMinimumHeight());
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mLeft = (getWidth() - mMeasuredMovieWidth) / 2f;
mTop = (getHeight() - mMeasuredMovieHeight) / 2f;
mVisible = getVisibility() == View.VISIBLE;
}
@Override
protected synchronized void onDraw(Canvas canvas) {
if (mMovie != null) {
if (!mPaused) {
updateAnimationTime();
drawMovieFrame(canvas);
invalidateView();
} else {
drawMovieFrame(canvas);
}
}
}
/**
* 重绘
*/
@SuppressLint("NewApi")
private void invalidateView() {
if (mVisible) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
postInvalidateOnAnimation();
} else {
invalidate();
}
}
}
/**
* 更新当前显示进度
*/
private void updateAnimationTime() {
long now = android.os.SystemClock.uptimeMillis();
// 如果第一帧,记录起始时间
if (mMovieStart == 0) {
mMovieStart = now;
}
// 取出动画的时长
int dur = mMovie.duration();
if (dur == 0) {
dur = DEFAULT_MOVIE_DURATION;
}
// 算出需要显示第几帧
mCurrentAnimationTime = (int) ((now - mMovieStart) % dur);
}
// 绘制图片
private void drawMovieFrame(Canvas canvas) {
// 设置要显示的帧,绘制即可
mMovie.setTime(mCurrentAnimationTime);
// canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.saveLayer(0, 0, getWidth(), getHeight(), null);
canvas.scale(mScale, mScale);
mMovie.draw(canvas, mLeft / mScale, mTop / mScale);
canvas.restore();
}
@SuppressLint("NewApi")
@Override
public void onScreenStateChanged(int screenState) {
super.onScreenStateChanged(screenState);
mVisible = screenState == SCREEN_STATE_ON;
invalidateView();
}
@SuppressLint("NewApi")
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
mVisible = visibility == View.VISIBLE;
invalidateView();
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
mVisible = visibility == View.VISIBLE;
invalidateView();
}
}
自定义属性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--progressbar带gif进度-->
<declare-styleable name="GifProgressBar">
<attr name="progress_gif" format="reference" />
<attr name="progress_width" format="dimension" />
</declare-styleable>
</resources>
布局代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ProgressBar
android:id="@+id/pk_progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="14dp"
android:layout_marginTop="150dp"
android:max="100"
android:progress="50"
android:progressDrawable="@drawable/auchor_pk_progress" />
<com.cgg.pkprogress.GifView
android:id="@+id/gif_view"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="149dp"
app:progress_gif="@raw/pking" />
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="@color/black"
android:padding="8dp"
android:text="随机进度 50%"
android:textColor="@color/white" />
</RelativeLayout>
主要代码:
package com.cgg.pkprogress;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
private ProgressBar progressBar;
private GifView gifView;
private TextView textView;
//每个百分比 的距离
private float scrollDistance;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = findViewById(R.id.pk_progressBar);
gifView = findViewById(R.id.gif_view);
textView = findViewById(R.id.tv);
// 得到progressBar控件的宽度
ViewTreeObserver vto2 = progressBar.getViewTreeObserver();
vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
progressBar.getViewTreeObserver().removeGlobalOnLayoutListener(this);
//得到屏幕的总宽度
int width = progressBar.getWidth();
scrollDistance = (float) ((1.0 / progressBar.getMax()) * width);
}
});
textView.setOnClickListener(view -> {
// 5 ~ 95 之间
setPKProgress(5 + new Random().nextInt(91));
});
}
private void setPKProgress(int value){
progressBar.setProgress(value);
gifView.setTranslationX((value - 50) * scrollDistance);
textView.setText("随机进度 " + value + "%");
}
}
下面来两个地址啦:
csdn地址:https://download.csdn.net/download/CGG92/19405631
github地址: