搬运_自定义 View 音乐播放的“条形与波浪”可视化效果

来源: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'
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值