来源:https://blog.csdn.net/aimeimeiTS/article/details/78173635
目录图片
代码
BarWavesVew/app/src/main/java/com/duan/barwavesvew/controller/下
MediaController.java
package com.duan.barwavesvew.controller;
import android.content.Context;
import android.database.Cursor;
import android.media.MediaPlayer;
import android.media.audiofx.Visualizer;
import android.net.Uri;
import android.provider.MediaStore;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import static android.media.audiofx.Visualizer.getMaxCaptureRate;
/**
* Created by DuanJiaNing on 2017/4/1.
*/
public class MediaController implements PlayControl {
private Context mContext;
private volatile int mCurrentSong;
private volatile boolean mPlayState = false; //true为正在播放
private final ArrayList<SongInfo> songs = new ArrayList<>();
private final MediaPlayer mPlayer;
private Visualizer mVisualizer;
private boolean mVisualizerEnable = false;
public MediaController(Context context) {
this.mContext = context.getApplicationContext();
Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, null);
while (cursor.moveToNext()) {
SongInfo info = new SongInfo();
info.setName(cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME)));
info.setSongPath(cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA)));
String albumID = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID));
info.setArtPicPath(getAlbumArtPicPath(albumID));
songs.add(info);
}
cursor.close();
if (songs.size() < 0) {
throw new IllegalStateException("没有歌曲");
}
mPlayer = new MediaPlayer();
try {
setCurrentSong(0);
} catch (IOException e) {
e.printStackTrace();
}
}
public interface onFftDataCaptureListener {
void onFftCapture(float[] fft);
}
/**
* 设置频谱回调
*
* @param size 传回的数组大小
* @param max 整体频率的大小,该值越小,传回数组的平均值越大,在 50 时效果较好。
* @param l 回调
*/
public void setupVisualizer(final int size, final int max, final onFftDataCaptureListener l) {
// 频率分之一是时间 赫兹=1/秒
mVisualizer = new Visualizer(mPlayer.getAudioSessionId());
mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[0]); //0为128;1为1024
mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() {
@Override
public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate) {
//快速傅里叶变换有关的数据
}
@Override
public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
//波形数据
byte[] model = new byte[fft.length / 2 + 1];
model[0] = (byte) Math.abs(fft[1]);
int j = 1;
for (int i = 2; i < size * 2; ) {
model[j] = (byte) Math.hypot(fft[i], fft[i + 1]);
i += 2;
j++;
}
float[] data = new float[size];
if (max != 0) {
for (int i = 0; i < size; i++) {
data[i] = (float) model[i] / max;
data[i] = data[i] < 0 ? 0 : data[i];
}
} else {
Arrays.fill(data, 0);
}
l.onFftCapture(data);
} // getMaxCaptureRate() -> 20000 最快
}, getMaxCaptureRate() / 8, false, true);
mVisualizer.setEnabled(false); //这个设置必须在参数设置之后,表示开始采样
}
public void setVisualizerEnable(boolean visualizerEnable) {
this.mVisualizerEnable = visualizerEnable;
if (mPlayer.isPlaying()) {
mVisualizer.setEnabled(mVisualizerEnable);
}
}
private String getAlbumArtPicPath(String albumId) {
String[] projection = {MediaStore.Audio.Albums.ALBUM_ART};
String imagePath = null;
Cursor cur = mContext.getContentResolver().query(
Uri.parse("content://media" + MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI.getPath() + "/" + albumId),
projection,
null,
null,
null);
if (cur.getCount() > 0 && cur.getColumnCount() > 0) {
cur.moveToNext();
imagePath = cur.getString(0);
}
cur.close();
return imagePath;
}
public ArrayList<SongInfo> getSongsList() {
return songs;
}
public int getCurrentSong() {
return mCurrentSong;
}
public int setCurrentSong(int index) throws IOException {
if (mCurrentSong != index) {
this.mCurrentSong = index;
changeSong();
}
return mCurrentSong;
}
public int setCurrentSong(SongInfo info) throws IOException {
this.mCurrentSong = songs.indexOf(info);
changeSong();
return mCurrentSong;
}
public boolean getPlayState() {
return mPlayState;
}
public void releaseMediaPlayer() {
if (mPlayer != null) {
mPlayer.release();
}
if (mVisualizer != null) {
mVisualizer.release();
}
}
@Override
public SongInfo preSong() throws IOException {
if (mCurrentSong == 0) {
mCurrentSong = songs.size() - 1;
} else {
mCurrentSong--;
}
changeSong();
return songs.get(mCurrentSong);
}
private synchronized void changeSong() throws IOException {
if (mPlayState) {
mPlayer.stop();
}
mPlayer.reset();
mPlayer.setDataSource(songs.get(mCurrentSong).getSongPath());
mPlayer.prepare();
if (mPlayState) {
mPlayer.start();
}
}
@Override
public SongInfo nextSong() throws IOException {
if (mCurrentSong == songs.size() - 1) {
mCurrentSong = 0;
} else {
mCurrentSong++;
}
changeSong();
return songs.get(mCurrentSong);
}
@Override
public synchronized boolean play() {
if (mPlayer.isPlaying())
return false;
else {
mPlayer.start();
if (mVisualizerEnable) {
mVisualizer.setEnabled(true);
}
mPlayState = true;
return true;
}
}
public boolean playing() {
return mPlayer.isPlaying();
}
@Override
public synchronized boolean stop() {
if (!mPlayer.isPlaying())
return false;
else {
mPlayer.pause();
if (mVisualizerEnable) {
mVisualizer.setEnabled(false);
}
mPlayState = false;
return true;
}
}
@Override
public void seekTo(int to) {
mPlayer.seekTo(to);
}
@Override
public int getCurrentPosition() {
return mPlayer.getCurrentPosition();
}
public int getDuration() {
return mPlayer.getDuration();
}
}
PlayControl.java
package com.duan.barwavesvew.controller;
import java.io.IOException;
/**
* Created by DuanJiaNing on 2017/4/1.
*/
public interface PlayControl {
SongInfo nextSong() throws IOException;
SongInfo preSong() throws IOException;
boolean play();
boolean stop();
void seekTo(int to);
int getCurrentPosition();
}
SongInfo.java
package com.duan.barwavesvew.controller;
/**
* Created by DuanJiaNing on 2017/4/1.
*/
public class SongInfo {
private String name;
private String songPath;
private String artPicPath;
public void setName(String name) {
this.name = name;
}
public void setSongPath(String songPath) {
this.songPath = songPath;
}
public void setArtPicPath(String artPicPath) {
this.artPicPath = artPicPath;
}
public String getName() {
return name;
}
public String getSongPath() {
return songPath;
}
public String getArtPicPath() {
return artPicPath;
}
}
ColorUtils.java
package com.duan.barwavesvew;
import android.graphics.Color;
/**
* Created by DuanJiaNing on 2017/4/2.
*/
public class ColorUtils {
/**
* 获得一个随机的颜色
*/
public static int getRandomColor() {
int r = (int) (Math.random() * 255); //产生一个255以内的整数
int g = (int) (Math.random() * 255); //产生一个255以内的整数
int b = (int) (Math.random() * 255); //产生一个255以内的整数
return Color.rgb(r, g, b);
}
/**
* 获得一个比较暗的随机颜色
*/
public static int getRandomBrunetColor() {
int r = (int) (Math.random() * 100); //产生一个100以内的整数
int g = (int) (Math.random() * 100);
int b = (int) (Math.random() * 100);
return Color.rgb(r, g, b);
}
/**
* r g b >= 160 时返回 true
*/
public static boolean isBrightSeriesColor(int color) {
double d = android.support.v4.graphics.ColorUtils.calculateLuminance(color);
if (d - 0.400 > 0.000001) {
return true;
} else {
return false;
}
}
}
Main2Activity.java
package com.duan.barwavesvew;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
}
}
MainActivity.java
package com.duan.barwavesvew;
import android.content.res.Configuration;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.FrameLayout;
import android.widget.ImageSwitcher;
import android.widget.ImageView;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.ViewSwitcher;
import com.duan.barwavesvew.controller.MediaController;
import com.duan.barwavesvew.controller.SongInfo;
import com.duan.library.BarWavesView;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
private ImageSwitcher switcher;
private TextView name;
private MediaController controller;
private Switch switcz;
private BarWavesView barWavesView;
private BarWavesView barWavesView_1;
private BarWavesView barWavesView_2;
private BarWavesView barWavesView_2_;
private BarWavesView barWavesView_3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
// testSetColor();
}
private void testSetColor() {
barWavesView.setBarColor(ColorUtils.getRandomColor());
// barWavesView.setWaveColor(ColorUtils.getRandomColor());
int[][] cs = new int[barWavesView.getWaveNumber()][2];
for (int i = 0; i < cs.length; i++) {
cs[i][0] = ColorUtils.getRandomColor();
cs[i][1] = ColorUtils.getRandomColor();
}
barWavesView.setWaveColor(cs);
}
private void init() {
barWavesView = (BarWavesView) findViewById(R.id.BarWavesView);
barWavesView_1 = (BarWavesView) findViewById(R.id.BarWavesView_1);
barWavesView_2 = (BarWavesView) findViewById(R.id.BarWavesView_2);
barWavesView_2_ = (BarWavesView) findViewById(R.id.BarWavesView_2_);
barWavesView_3 = (BarWavesView) findViewById(R.id.BarWavesView_3);
switcher = (ImageSwitcher) findViewById(R.id.image_switch);
name = (TextView) findViewById(R.id.name);
switcz = (Switch) findViewById(R.id.visuall);
switcz.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
controller.setVisualizerEnable(isChecked);
}
});
switcher.setFactory(new ViewSwitcher.ViewFactory() {
@Override
public View makeView() {
ImageView view = new ImageView(MainActivity.this);
view.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
view.setScaleType(ImageView.ScaleType.CENTER_CROP);
view.setAlpha(0.4f);
return view;
}
});
controller = new MediaController(this);
controller.setupVisualizer(barWavesView.getWaveNumber(), 50, new MediaController.onFftDataCaptureListener() {
@Override
public void onFftCapture(float[] fft) {
barWavesView.setWaveHeight(fft);
barWavesView_1.setWaveHeight(fft);
barWavesView_2.setWaveHeight(fft);
barWavesView_2_.setWaveHeight(fft);
barWavesView_3.setWaveHeight(fft);
// testSetColor();
}
});
controller.setVisualizerEnable(true);
update(controller.getSongsList().get(0));
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
@Override
protected void onDestroy() {
super.onDestroy();
controller.releaseMediaPlayer();
}
public void onPlay(View v) {
if (controller.playing()) {
controller.stop();
((Button) v).setText("播放");
} else {
controller.play();
((Button) v).setText("暂停");
}
}
public void onNext(View v) {
try {
update(controller.nextSong());
} catch (IOException e) {
e.printStackTrace();
}
}
private void update(SongInfo song) {
name.setText(song.getName());
BitmapDrawable d = new BitmapDrawable(getResources(), BitmapFactory.decodeFile(song.getArtPicPath()));
switcher.setImageDrawable(d);
}
public void onPre(View v) {
try {
update(controller.preSong());
} catch (IOException e) {
e.printStackTrace();
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
android:background="@color/colorPrimary"
android:saveEnabled="true"
tools:context="com.duan.barwavesvew.MainActivity">
<ImageSwitcher
android:id="@+id/image_switch"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80000000"
android:inAnimation="@android:anim/fade_in"
android:outAnimation="@android:anim/fade_out" />
<com.duan.library.BarWavesView
android:id="@+id/BarWavesView_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="-230dp"
app:waveNumber="35"
app:waveWidth="30dp"
app:waveRange="600dp"
app:waveMinHeight="0dp"
app:waveInterval="5dp"
app:waveColor="#7eaeaeae"
app:barHeight="0dp"
app:fallAutomatically="false"
/>
<com.duan.library.BarWavesView
android:id="@+id/BarWavesView_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:rotation="210"
android:layout_marginLeft="-200dp"
android:layout_marginTop="140dp"
app:waveNumber="35"
app:waveWidth="20dp"
app:waveRange="300dp"
app:waveMinHeight="10dp"
app:barColor="#ffffff"
app:waveColor="#ff00bb"
app:barHeight="2px"
app:fallAutomatically="false"
/>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.duan.library.BarWavesView
android:id="@+id/BarWavesView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:barColor="#d1d1d1"
app:barHeight="25dp"
app:layout_constraintBottom_toTopOf="@+id/name"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:waveColor="#ffd500"
app:fallAutomatically="false"
app:waveInterval="3dp"
app:waveMinHeight="30dp"
app:waveNumber="35"
app:waveRange="120dp"
app:waveWidth="13dp" />
<com.duan.library.BarWavesView
android:id="@+id/BarWavesView_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:waveNumber="35"
app:waveWidth="7dp"
app:waveRange="60dp"
app:waveMinHeight="10dp"
app:waveInterval="0dp"
app:barColor="#41000000"
app:waveColor="#00ffaa"
app:barHeight="0dp"
android:layout_marginRight="8dp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp" />
<com.duan.library.BarWavesView
android:id="@+id/BarWavesView_2_"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:waveNumber="35"
app:waveWidth="7dp"
app:waveRange="60dp"
android:rotationX="180"
app:waveMinHeight="10dp"
app:waveInterval="0dp"
app:barColor="#41000000"
app:waveColor="#4e00ffaa"
app:barHeight="0dp"
android:layout_marginTop="0dp"
app:layout_constraintTop_toBottomOf="@+id/BarWavesView_2"
android:layout_marginRight="8dp"
app:layout_constraintRight_toRightOf="parent" />
<Switch
android:id="@+id/visuall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
android:checked="true"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:background="#51ffffff"
android:padding="5dp"
android:text="Hello World!"
app:layout_constraintBottom_toTopOf="@+id/play"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<Button
android:id="@+id/pre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:onClick="onPre"
android:text="上一曲"
app:layout_constraintBottom_toBottomOf="@+id/play"
app:layout_constraintRight_toLeftOf="@+id/play"
app:layout_constraintTop_toTopOf="@+id/play"
app:layout_constraintVertical_bias="0.0" />
<Button
android:id="@+id/next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:onClick="onNext"
android:text="下一曲"
app:layout_constraintBottom_toBottomOf="@+id/play"
app:layout_constraintLeft_toRightOf="@+id/play"
app:layout_constraintTop_toTopOf="@+id/play"
app:layout_constraintVertical_bias="0.0" />
<Button
android:id="@+id/play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:onClick="onPlay"
android:text="播放"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<android.support.constraint.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5"
tools:layout_editor_absoluteX="192dp"
tools:layout_editor_absoluteY="81dp" />
</android.support.constraint.ConstraintLayout>
</FrameLayout>
activity_main2.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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="com.duan.barwavesvew.Main2Activity">
<com.duan.library.BarWavesView
android:id="@+id/BarWavesView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:barColor="#d1d1d1"
app:barHeight="25dp"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:waveColor="#ffd500"
app:fallAutomatically="false"
app:waveInterval="3dp"
app:waveMinHeight="30dp"
app:waveNumber="35"
app:waveRange="120dp"
app:waveWidth="13dp"
app:layout_constraintBottom_toBottomOf="parent" />
</android.support.constraint.ConstraintLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.duan.barwavesvew">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".Main2Activity"/>
</application>
</manifest>
BarWavesVew/library/src/main/java/com/duan/library/BarWavesView.java
package com.duan.library;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.ColorInt;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import java.util.Arrays;
/**
* Created by DuanJiaNing on 2017/9/24.
* 当为控件指定宽和高时,mWaveInterval 将由控件计算
* 不支持 paddding
*/
public class BarWavesView extends View {
/**
* 横条颜色
*/
private int mBarColor;
/**
* 横条高度
* fix
*/
private int mBarHeight;
/**
* 波浪条最小高度
* fix
*/
private int mWaveMinHeight;
/**
* 波浪条极差(最高与最低的差值)
* fix
*/
private int mWaveRange;
/**
* 波浪条宽度
* fix
*/
private int mWaveWidth;
/**
* 波浪条数量
* fix
*/
private int mWaveNumber;
/**
* 波浪条间隔
* fix
*/
private int mWaveInterval;
/**
* 波浪条落下时是否使用动画
*/
private boolean mFallAnimEnable = true;
/**
* 波浪条坠落时间(毫秒)
*/
private int mFallDuration;
private final Paint mPaint = new Paint();
private int[][] mWaveColors;
private float[] mWaveHeight;
private ValueAnimator mAnim;
private final static int sDEFAULT_BAR_COLOR = Color.LTGRAY;
private final static int sDEFAULT_WAVE_COLOR = Color.YELLOW;
private final static int sDEFAULT_FALL_ANIM_DURATION = 1300;
/**
* xml 中指定的值小于以下值时无效
*/
private final static int sMIN_WAVE_NUMBER = 13;
private final static int sMIN_BAR_HEIGHT = 0;
private final static int sMIN_WAVE_HEIGHT = 0;
private final static int sMIN_WAVE_RANGE = 10;
private final static int sMIN_WAVE_INTERVAL = 0;
private final static int sMIN_WAVE_WIDTH = 5;
// 控件的宽度由波浪条数量、宽度、间距共同决定
private final static int sMIN_WIDTH = sMIN_WAVE_NUMBER * sMIN_WAVE_WIDTH + (sMIN_WAVE_NUMBER - 1) * sMIN_WAVE_INTERVAL;
private final static int sMIN_HEIGHT = sMIN_WAVE_HEIGHT + sMIN_WAVE_RANGE + sMIN_BAR_HEIGHT;
public BarWavesView(Context context, int waveNumber) {
super(context);
this.mWaveNumber = waveNumber;
this.mPaint.setAntiAlias(true);
this.mBarColor = sDEFAULT_BAR_COLOR;
this.mFallAnimEnable = true;
this.mFallDuration = sDEFAULT_FALL_ANIM_DURATION;
setSaveEnabled(true);
initAnim(mFallDuration);
setWaveColors(sMIN_WAVE_NUMBER, sDEFAULT_WAVE_COLOR);
setWaveHeights(0);
}
private void initAnim(int dur) {
mAnim = ObjectAnimator.ofFloat(1.0f, 0.0f);
mAnim.setInterpolator(new AccelerateDecelerateInterpolator());
mAnim.setDuration(dur);
mAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float v = (float) animation.getAnimatedValue();
for (int i = 0; i < mWaveHeight.length; i++) {
mWaveHeight[i] *= v;
}
invalidate();
}
});
}
public BarWavesView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public BarWavesView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setSaveEnabled(true);
mPaint.setAntiAlias(true);
final TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.BarWavesView, defStyleAttr, 0);
int tempWaveColor = array.getColor(R.styleable.BarWavesView_waveColor, sDEFAULT_WAVE_COLOR);
mBarColor = array.getColor(R.styleable.BarWavesView_barColor, sDEFAULT_BAR_COLOR);
mBarHeight = array.getDimensionPixelSize(R.styleable.BarWavesView_barHeight, 20);
mBarHeight = mBarHeight < sMIN_BAR_HEIGHT ? sMIN_BAR_HEIGHT : mBarHeight;
mWaveRange = array.getDimensionPixelSize(R.styleable.BarWavesView_waveRange, 30);
mWaveRange = mWaveRange < sMIN_WAVE_RANGE ? sMIN_WAVE_RANGE : mWaveRange;
mWaveMinHeight = array.getDimensionPixelSize(R.styleable.BarWavesView_waveMinHeight, 10);
mWaveMinHeight = mWaveMinHeight < sMIN_WAVE_HEIGHT ? sMIN_WAVE_HEIGHT : mWaveMinHeight;
mWaveWidth = array.getDimensionPixelSize(R.styleable.BarWavesView_waveWidth, 10);
mWaveWidth = mWaveWidth < sMIN_WAVE_WIDTH ? sMIN_WAVE_WIDTH : mWaveWidth;
mWaveInterval = array.getDimensionPixelSize(R.styleable.BarWavesView_waveInterval, 8);
mWaveInterval = mWaveInterval < sMIN_WAVE_INTERVAL ? sMIN_WAVE_INTERVAL : mWaveInterval;
mWaveNumber = array.getInteger(R.styleable.BarWavesView_waveNumber, sMIN_WAVE_NUMBER);
mWaveNumber = mWaveNumber < sMIN_WAVE_NUMBER ? sMIN_WAVE_NUMBER : mWaveNumber;
mFallAnimEnable = array.getBoolean(R.styleable.BarWavesView_fallAutomatically, true);
mFallDuration = array.getInteger(R.styleable.BarWavesView_fallDuration, sDEFAULT_FALL_ANIM_DURATION);
//释放资源
array.recycle();
setWaveColors(mWaveNumber, tempWaveColor);
setWaveHeights(0);
if (mFallAnimEnable) {
initAnim(mFallDuration);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize < sMIN_WIDTH ? sMIN_WIDTH : widthSize;
// 手动计算波浪条间距,即宽为指定长度时,波浪条间距自动计算(xml 中指定将失效)
adjustWidth(width);
} else {//xml中宽度设为warp_content
width = mWaveWidth * mWaveNumber + mWaveInterval * (mWaveNumber - 1);
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize < sMIN_HEIGHT ? sMIN_HEIGHT : heightSize;
adjustHeight(height);
} else {
height = mWaveMinHeight + mWaveRange + mBarHeight;
}
setMeasuredDimension(width, height);
}
private void adjustWidth(int width) {
while (width < mWaveInterval * (mWaveNumber - 1) + mWaveWidth * mWaveNumber) {
if (mWaveInterval > sMIN_WAVE_INTERVAL) {
mWaveInterval--; // 首选调整波浪条间距
} else {
if (mWaveWidth > sMIN_WAVE_WIDTH) {
mWaveWidth--; // 其次选择调整波浪条宽度
} else {
width++; // 再次选择调整设置的宽度
}
}
}
}
private void adjustHeight(int height) {
while (mWaveMinHeight + mWaveRange + mBarHeight > height) {
if (mBarHeight > sMIN_BAR_HEIGHT) {
mBarHeight--; //首选调整横条高度
continue;
}
if (mWaveMinHeight > sMIN_WAVE_HEIGHT) {
mWaveMinHeight--; // 其次选择调整波浪条的最小高度
continue;
}
if (mWaveRange > sMIN_WAVE_RANGE) {
mWaveRange--; // 再次选择调整波浪条极差
}
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
drawBar(canvas);
drawWaves(canvas);
}
private void drawWaves(Canvas canvas) {
for (int i = 0; i < mWaveNumber; i++) {
float left = mWaveWidth * i + mWaveInterval * i;
float right = left + mWaveWidth;
float bottom = getHeight() - mBarHeight;
float fs = mWaveHeight[i];
float top;
top = bottom - mWaveMinHeight - (fs * mWaveRange);
LinearGradient lg = new LinearGradient(
left, bottom,
right, top,
mWaveColors[i],
null,
Shader.TileMode.CLAMP
);
mPaint.setAlpha(255);
mPaint.setShader(lg);
canvas.drawRect(left, top, right, bottom, mPaint);
}
}
private void drawBar(Canvas canvas) {
mPaint.setShader(null);
mPaint.setAlpha(255);
mPaint.setColor(mBarColor);
float right = mWaveInterval * (mWaveNumber - 1) + mWaveWidth * mWaveNumber;
float top = getHeight() - mBarHeight;
canvas.drawRect(0, top, right, getHeight(), mPaint);
}
/**
* 设置横条颜色
*
* @param color 颜色
*/
public void setBarColor(@ColorInt int color) {
this.mBarColor = color;
invalidate();
}
/**
* 统一设置所有波浪条的颜色
*
* @param color 颜色
*/
public void setWaveColor(@ColorInt int color) {
setWaveColors(mWaveNumber, color);
invalidate();
}
/**
* 设置每一个波浪条的渐变颜色
*
* @param color 颜色
*/
public void setWaveColor(int[][] color) {
if (color == null || color.length < mWaveNumber || color[0].length < 2) {
return;
}
setWaveColors(mWaveNumber, color);
invalidate();
}
/**
* 设置每一个波浪条的纯颜色
*
* @param color 颜色
*/
public void setWaveColor(int[] color) {
if (color == null || color.length < mWaveNumber) {
return;
}
int[][] cs = new int[color.length][2];
for (int i = 0; i < cs.length; i++) {
cs[i][0] = color[i];
cs[i][1] = color[i];
}
setWaveColors(cs.length, cs);
invalidate();
}
/**
* 改变波浪条的高度
*
* @param hs 数值介于 0.0 - 1.0 的浮点数组,当值为 1.0 时波浪条将完全绘制(最高),0.0 时波浪条只绘制最低高度(最低)。
*/
public void setWaveHeight(float[] hs) {
if (mFallAnimEnable && mAnim != null && (mAnim.isStarted() || mAnim.isRunning())) {
mAnim.cancel();
}
setWaveHeights(hs);
invalidate();
if (mFallAnimEnable) {
if (mAnim == null) {
initAnim(mFallDuration);
}
mAnim.start();
}
}
public void setFallAutomatically(boolean enable) {
this.mFallAnimEnable = enable;
if (mFallAnimEnable && mAnim == null) {
initAnim(mFallDuration);
}
}
public void setFallDuration(int duration) {
this.mFallDuration = duration;
this.mFallAnimEnable = true;
releaseAnim();
initAnim(duration);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
releaseAnim();
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.barColor = mBarColor;
ss.fallAnimEnable = mFallAnimEnable ? 1 : 0;
ss.fallDuration = mFallDuration;
ss.waveColors = mWaveColors;
ss.waveHeight = mWaveHeight;
return ss;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
mBarColor = ss.barColor;
mFallAnimEnable = ss.fallAnimEnable == 1;
mFallDuration = ss.fallDuration;
mWaveColors = ss.waveColors;
mWaveHeight = ss.waveHeight;
if (mFallAnimEnable) {
setFallDuration(mFallDuration);
}
requestLayout();
}
public static class SavedState extends BaseSavedState {
int barColor;
int fallAnimEnable;
int fallDuration;
int[][] waveColors;
float[] waveHeight;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
barColor = in.readInt();
fallAnimEnable = in.readInt();
fallDuration = in.readInt();
waveColors = (int[][]) in.readValue(null);
in.readFloatArray(waveHeight);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(barColor);
out.writeInt(fallAnimEnable);
out.writeInt(fallDuration);
out.writeValue(waveColors);
out.writeFloatArray(waveHeight);
}
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
@Override
public String toString() {
return "SavedState{" +
"barColor=" + barColor +
", fallAnimEnable=" + fallAnimEnable +
", fallDuration=" + fallDuration +
", waveColors=" + Arrays.toString(waveColors) +
", waveHeight=" + Arrays.toString(waveHeight) +
'}';
}
}
private void releaseAnim() {
if (mAnim != null) {
if (mAnim.isStarted() || mAnim.isRunning()) {
mAnim.cancel();
}
mAnim.removeAllUpdateListeners();
mAnim.removeAllListeners();
mAnim = null;
}
}
private void setWaveHeights(float[] hs) {
if (hs == null || hs.length != mWaveNumber) {
return;
}
mWaveHeight = hs;
}
private void setWaveHeights(float h) {
if (h > 1 || h < 0) {
return;
}
mWaveHeight = new float[mWaveNumber];
Arrays.fill(mWaveHeight, h);
}
// len 不能小于 mWaveNumber 数组第二维长度不能小于 2
private void setWaveColors(int len, int[][] color) {
mWaveColors = new int[len][color[0].length];
for (int i = 0; i < mWaveColors.length; i++) {
for (int j = 0; j < mWaveColors[i].length; j++) {
mWaveColors[i][j] = color[i][j];
}
}
}
// len 不能小于 mWaveNumber
private void setWaveColors(int len, int color) {
mWaveColors = new int[len][2];
for (int i = 0; i < mWaveColors.length; i++) {
mWaveColors[i][0] = color;
mWaveColors[i][1] = color;
}
}
public int getBarColor() {
return mBarColor;
}
public int getBarHeight() {
return mBarHeight;
}
public int getWaveMinHeight() {
return mWaveMinHeight;
}
public int getWaveMaxHeight() {
return mWaveMinHeight + mWaveRange;
}
public int getWaveWidth() {
return mWaveWidth;
}
public int getWaveNumber() {
return mWaveNumber;
}
public float[] getWaveHeight() {
return mWaveHeight;
}
}
BarWavesVew/library/src/main/res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BarWavesView">
<attr name="barColor" format="color" /> <!--横条颜色-->
<!--确定控件绘制高度 未指定明确高度时-->
<attr name="barHeight" format="dimension" /> <!--横条高度-->
<attr name="waveRange" format="dimension" /> <!--波浪条极差(最高与最低的差值)-->
<attr name="waveMinHeight" format="dimension" /> <!--波浪条最小高度-->
<!--确定控件绘制宽度 未指定明确宽度时-->
<attr name="waveColor" format="color" /> <!--波浪条颜色-->
<attr name="waveWidth" format="dimension" /> <!--波浪条宽度-->
<attr name="waveNumber" format="integer" /> <!--波浪条数量-->
<attr name="waveInterval" format="dimension" /> <!--波浪间隔-->
<attr name="fallAutomatically" format="boolean"/> <!--波浪条高度大于最小高度时自动坠落-->
<attr name="fallDuration" format="integer" /> <!--波浪条坠落事件(毫秒,默认 1300)-->
</declare-styleable>
</resources>
BarWavesVew/library/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.duan.library">
<application android:allowBackup="true" android:label="@string/app_name"
android:supportsRtl="true">
</application>
</manifest>
BarWavesVew/app/build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "26.0.1"
defaultConfig {
applicationId "com.duan.barwavesvew"
minSdkVersion 14
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile project(':library')
compile 'com.android.support:appcompat-v7:26.+'
compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha9'
testCompile 'junit:junit:4.12'
}