Android 音乐播放器实现底部播放器、全屏播放器一体化(Fragment+ BottomSheet实现)

一个基础的一体化播放控制器,用于提升用户体验,减少界面切换带来的不便,以及提高开发效率和性能。

依赖添加

implementation 'com.github.bumptech.glide:glide:4.16.0'
implementation 'androidx.cardview:cardview:1.0.0'

最终效果,底部控制器、过渡动画、全屏控制器
Android 音乐播放器实现底部播放器、全屏播放器一体化(Fragment+ BottomSheet实现)_第1张图片Android 音乐播放器实现底部播放器、全屏播放器一体化(Fragment+ BottomSheet实现)_第2张图片
Android 音乐播放器实现底部播放器、全屏播放器一体化(Fragment+ BottomSheet实现)_第3张图片

一 整体思路

项目已上传至github,musicplayercontrol模块
https://github.com/yyoujinga/smalldemos
播放器首页一般为歌曲list和底部控制器,点击底部控制器可打开全屏播放控制器,为实现一体化,底部控制器和全屏播放控制器同处一个fragment文件。
因此
1activity中整体xml布局可分为recycleview和装载控制器的控件(fragmentcontainer),此处我使用cardview实现。
2activity文件中,动态使用bottomsheet实现控制器的状态,折叠状态对应底部播放器,展开对应全屏播放器,设置默认高度为底部播放器高度,并初始化fragment,将音乐数据装载至recycleview。
3fragment中整体xml布局可分为底部控制器和全屏播放控制器,其中全屏默认gone,只显示底部控制器,且底部控制器需在布局的top,否则会出现在首页底部控制器为空白的状况。
4fragment文件中,设置展开、折叠的动画(也可额外添加slide动画,实现滑动效果),控制底部、全屏播放控制器的显示。
5其他,可用spf保存播放器状态,但这方面需求不多,我实现的比较简单。
bottomsheet具体介绍可参考以下博文
https://blog.csdn.net/m0_73986294/article/details/134366315

二 代码

1 PlayerActivity


public class PlayerActivity extends AppCompatActivity {

    private PlayerFragment playerFragment;
    private PreferencesUtility preferencesUtility;
    private BottomSheetBehavior<View> behavior;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_player);

        preferencesUtility = PreferencesUtility.getInstance(this);

        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
        recyclerView.setAdapter(new MusicAdapter(MusicInfo.getTestData()));
        // 初始化 Fragment
        if (savedInstanceState == null) {
            playerFragment = new PlayerFragment();
            getSupportFragmentManager()
                    .beginTransaction()
                    .replace(R.id.fragment_container, playerFragment)
                    .commit();
        }

        // 设置 BottomSheet
        View bottomSheet = findViewById(R.id.fragment_container);
        behavior = BottomSheetBehavior.from(bottomSheet);
        behavior.setPeekHeight(getResources().getDimensionPixelSize(R.dimen.player_min_height));


        // 设置点击事件来展开 BottomSheet
        bottomSheet.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 只有在当前状态是 COLLAPSED 时才展开
                if (behavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
                    behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
                }
            }
        });

        behavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
            @Override
            public void onStateChanged( View view, int newState) {
                if (newState == BottomSheetBehavior.STATE_EXPANDED) {
                    preferencesUtility.setPlayerState(true);
                    if (playerFragment != null) {
                        playerFragment.onExpanded();
                    }
                } else if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
                    preferencesUtility.setPlayerState(false);
                    if (playerFragment != null) {
                        playerFragment.onCollapsed();
                    }
                }
            }

            @Override
            public void onSlide( View view, float slideOffset) {
                playerFragment.onSlide(slideOffset);
            }
        });
    }

    @Override
    public void onBackPressed() {
        if (behavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
            behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        } else {
            super.onBackPressed();
        }
    }
}

xml文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 主内容区域 -->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:paddingBottom="64dp"
        android:layout_marginBottom="64dp"/>

    <!-- 播放器容器 -->
    <androidx.cardview.widget.CardView
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        app:behavior_peekHeight="64dp"
        app:behavior_hideable="false"
        app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"/>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

2PlayerFragment


public class PlayerFragment extends Fragment {

    private View rootView;
    private View miniPlayer;
    private View fullPlayer;
    private ImageView albumArtMini;
    private ImageView albumArtFull;
    private TextView titleMini;
    private TextView titleFull;
    private ImageButton playPauseButton;
    private SeekBar seekBar;

    private MediaPlayer mediaPlayer;
    private boolean isPlaying = false;
    private boolean isPrepared = false;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        rootView = inflater.inflate(R.layout.fragment_player, container, false);
        initViews();
        initdata();
        setupListeners();
        return rootView;
    }

    private void initdata() {

    }

    private void initViews() {
        miniPlayer = rootView.findViewById(R.id.mini_player);
        fullPlayer = rootView.findViewById(R.id.full_player);
        albumArtMini = rootView.findViewById(R.id.album_art_mini);
        albumArtFull = rootView.findViewById(R.id.album_art_full);
        titleMini = rootView.findViewById(R.id.title_mini);
        titleFull = rootView.findViewById(R.id.title_full);
        playPauseButton = rootView.findViewById(R.id.play_pause_button);
        seekBar = rootView.findViewById(R.id.seek_bar);

        // 初始状态
        fullPlayer.setVisibility(View.GONE);
        fullPlayer.setAlpha(0);
        miniPlayer.setVisibility(View.VISIBLE);
        miniPlayer.setAlpha(1);
    }

    private void setupListeners() {
        playPauseButton.setOnClickListener(v -> togglePlayPause());

        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (fromUser && mediaPlayer != null) {
                    mediaPlayer.seekTo(progress);
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {}

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {}
        });
    }

    public void onExpanded() {
        miniPlayer.animate()
                .alpha(0)
                .setDuration(200)
                .withEndAction(() -> miniPlayer.setVisibility(View.GONE));

        fullPlayer.setVisibility(View.VISIBLE);
        fullPlayer.animate()
                .alpha(1)
                .setDuration(200);
    }

    public void onCollapsed() {
        fullPlayer.animate()
                .alpha(0)
                .setDuration(200)
                .withEndAction(() -> fullPlayer.setVisibility(View.GONE));

        miniPlayer.setVisibility(View.VISIBLE);
        miniPlayer.animate()
                .alpha(1)
                .setDuration(200);
    }

    public void onSlide(float slideOffset) {
        miniPlayer.setAlpha(1 - slideOffset);
        fullPlayer.setAlpha(slideOffset);

        if (slideOffset > 0 && fullPlayer.getVisibility() == View.GONE) {
            fullPlayer.setVisibility(View.VISIBLE);
        }
    }

    private void togglePlayPause() {
        if (mediaPlayer != null) {
            if (isPlaying) {
                mediaPlayer.pause();
                playPauseButton.setImageResource(R.drawable.ic_play);
            } else {
                mediaPlayer.start();
                playPauseButton.setImageResource(R.drawable.ic_pause);
            }
            isPlaying = !isPlaying;
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mediaPlayer != null) {
            mediaPlayer.release();
            mediaPlayer = null;
        }
    }
}

xml文件



    

    

    





    

    

    

    

        

        

        

    


3 其他文件

adapter和musicinfo文件比较简单

public class PreferencesUtility {
    private static PreferencesUtility sInstance;
    private final SharedPreferences mPreferences;

    private PreferencesUtility(Context context) {
        mPreferences = PreferenceManager.getDefaultSharedPreferences(context);
    }

    public static PreferencesUtility getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new PreferencesUtility(context.getApplicationContext());
        }
        return sInstance;
    }

    public boolean isFirstTime() {
        return mPreferences.getBoolean("first_time", true);
    }

    public void setFirstTime(boolean firstTime) {
        final SharedPreferences.Editor editor = mPreferences.edit();
        editor.putBoolean("first_time", firstTime);
        editor.apply();
    }

    // 播放器状态
    public void setPlayerState(boolean isExpanded) {
        final SharedPreferences.Editor editor = mPreferences.edit();
        editor.putBoolean("player_expanded", isExpanded);
        editor.apply();
    }

    public boolean getPlayerState() {
        return mPreferences.getBoolean("player_expanded", false);
    }

    // 播放模式
    public void setPlayMode(int mode) {
        final SharedPreferences.Editor editor = mPreferences.edit();
        editor.putInt("play_mode", mode);
        editor.apply();
    }

    public int getPlayMode() {
        return mPreferences.getInt("play_mode", 0);
    }
}

你可能感兴趣的:(Android 音乐播放器实现底部播放器、全屏播放器一体化(Fragment+ BottomSheet实现))