android onfling,LinearLayout上onFling事件失效问题

本文介绍了如何在Android中实现手势检测,通过实现OnGestureListener和OnTouchListener接口,重写相关方法,尤其是确保onDown()返回true以触发onFling事件。详细步骤包括设置布局的触摸事件,创建GestureDetector对象,并在onTouch方法中调用其onTouchEvent()。在onFling()方法中,判断滑动方向,分别处理向左和向右的滑动操作。

1.  写一个类,实现OnGestureListener, OnTouchListener接口。重写里面的方法,记得把onDown()方法return true;这样才能触发onFling事件。

2.  设置layout的onTouch事件:

LinearLayout layout = (LinearLayout) findViewById(R.id.mylayout); layout.setOnTouchListener(this);

3.  在touch时调用如下:

在onTouch方法中return detector.onTouchEvent(event);

//其中detector是:GestureDetector detector = new GestureDetector(this);

4.  重写onFling()方法:

public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,

float velocityY) {

if(e1.getX() - e2.getX() > 50 && Math.abs(velocityX)>0){

System.out.println("Left.......");//向左滑动

}else if(e2.getX()-e1.getX()>50 && Math.abs(velocityX)>0){

System.out.println("Right......");//向右滑动

}

return false;

}

package com.weishitech.qichechangtingyinyue.utils; import android.content.Context; import android.media.AudioAttributes; import android.media.MediaPlayer; import androidx.annotation.NonNull; import com.weishitech.qichechangtingyinyue.bean.MusicBean; import java.util.List; public class MusicPlayerManager { private MediaPlayer mediaPlayer; private OnPlaybackStateChangedListener listener; private int currentPlayingPosition = -1; // 主列表位置 private boolean isPlayingFromPrivate = false; private List<MusicBean.DataBean> currentExternalList; private int currentExternalIndex = -1; private String currentPlayingUrl = null; // ✅ 使用 tag 标识当前播放的是哪个外部歌单(避免引用失效) private String currentPlaylistTag; // "private", "qingyin", "xiaozhong" public boolean isPlayingFromPrivate() { return isPlayingFromPrivate; } public void setPlayingFromPrivate(boolean flag) { this.isPlayingFromPrivate = flag; } public String getCurrentPlayingMusicUrl() { return currentPlayingUrl; } public int getCurrentExternalIndex() { return currentExternalIndex; } public String getCurrentPlaylistTag() { return currentPlaylistTag; } public interface OnPlaybackStateChangedListener { void onPlaying(int position); void onPaused(); void onCompletion(); void onError(String errorMsg); } public void setOnPlaybackStateChangedListener(OnPlaybackStateChangedListener listener) { this.listener = listener; } // 播放主列表歌曲 public void play(Context context, int position, String musicUrl) { if (position == currentPlayingPosition && mediaPlayer != null && mediaPlayer.isPlaying()) { return; } try { release(); mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(musicUrl); mediaPlayer.setAudioAttributes(new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .setUsage(AudioAttributes.USAGE_MEDIA) .build()); mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(mp -> { mp.start(); currentPlayingPosition = position; currentPlayingUrl = musicUrl; isPlayingFromPrivate = false; currentPlaylistTag = null; // 清除外部标签 if (listener != null) listener.onPlaying(position); }); setupListeners(); } catch (Exception e) { e.printStackTrace(); if (listener != null) listener.onError("无法播放音乐"); } } /** * 播放外部列表,支持从中断处恢复 */ public void playFromExternalList(Context context, @NonNull List<MusicBean.DataBean> list, int startIndex, String tag) { try { boolean isSameList = tag.equals(currentPlaylistTag); // ✅ 改用 tag 判断 int playIndex = isSameList && currentExternalIndex >= 0 ? currentExternalIndex : startIndex; if (playIndex < 0 || playIndex >= list.size()) return; MusicBean.DataBean bean = list.get(playIndex); release(); mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(bean.getMusic()); mediaPlayer.setAudioAttributes(new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .setUsage(AudioAttributes.USAGE_MEDIA) .build()); mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(mp -> { mp.start(); currentPlayingPosition = -1; currentPlayingUrl = bean.getMusic(); isPlayingFromPrivate = true; currentExternalList = list; currentExternalIndex = playIndex; currentPlaylistTag = tag; // ✅ 记录 tag if (listener != null) listener.onPlaying(-1); }); setupListeners(); } catch (Exception e) { e.printStackTrace(); if (listener != null) listener.onError("无法播放音乐"); } } private void setupListeners() { mediaPlayer.setOnCompletionListener(mp -> { if (listener != null) listener.onCompletion(); }); mediaPlayer.setOnErrorListener((mp, what, extra) -> { if (listener != null) listener.onError("播放错误: " + what); return true; }); } public void pause() { if (mediaPlayer != null && mediaPlayer.isPlaying()) { mediaPlayer.pause(); if (listener != null) listener.onPaused(); } } public void resume() { if (mediaPlayer != null && !mediaPlayer.isPlaying()) { mediaPlayer.start(); if (listener != null) listener.onPlaying(isPlayingFromPrivate ? -1 : currentPlayingPosition); } } public boolean isPlaying() { return mediaPlayer != null && mediaPlayer.isPlaying(); } public int getCurrentPlayingPosition() { return currentPlayingPosition; } public boolean playNextInExternalList() { if (currentExternalList == null || currentExternalIndex < 0) return false; int nextIndex = currentExternalIndex + 1; if (nextIndex >= currentExternalList.size()) return false; MusicBean.DataBean bean = currentExternalList.get(nextIndex); try { release(); mediaPlayer = new MediaPlayer(); mediaPlayer.setDataSource(bean.getMusic()); mediaPlayer.setAudioAttributes(new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .setUsage(AudioAttributes.USAGE_MEDIA) .build()); mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(mp -> { mp.start(); currentPlayingPosition = -1; currentPlayingUrl = bean.getMusic(); currentExternalIndex = nextIndex; isPlayingFromPrivate = true; // 不改变 tag if (listener != null) listener.onPlaying(-1); }); setupListeners(); return true; } catch (Exception e) { e.printStackTrace(); return false; } } public void release() { if (mediaPlayer != null) { mediaPlayer.release(); mediaPlayer = null; } currentPlayingPosition = -1; currentPlayingUrl = null; currentExternalIndex = -1; currentPlaylistTag = null; currentExternalList = null; } } package com.weishitech.qichechangtingyinyue.fragment.home; import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; import android.os.Bundle; import android.text.TextUtils; import android.util.DisplayMetrics; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearSmoothScroller; import androidx.recyclerview.widget.RecyclerView; import com.hfd.common.base.BaseFragment; import com.hfd.common.net.GenericsCallback; import com.hfd.common.net.HttpBuiler; import com.hfd.common.net.JsonGenericsSerializator; import com.hfd.common.net.Url; import com.hfd.common.util.ToastUtil; import com.weishitech.qichechangtingyinyue.R; import com.weishitech.qichechangtingyinyue.bean.MusicBean; import com.weishitech.qichechangtingyinyue.fragment.Adapter.PrivateAdapter; import com.weishitech.qichechangtingyinyue.fragment.Adapter.QyAdapter; import com.weishitech.qichechangtingyinyue.fragment.Adapter.SongTopAdapter; import com.weishitech.qichechangtingyinyue.fragment.Adapter.XzAdapter; import com.weishitech.qichechangtingyinyue.utils.DisplayUtils; import com.weishitech.qichechangtingyinyue.utils.LeftGravitySnapHelper; import com.weishitech.qichechangtingyinyue.utils.MusicPlayerManager; import com.weishitech.qichechangtingyinyue.utils.OnMultiClickListener; import com.weishitech.qichechangtingyinyue.utils.RotationAnimatorHelper; import com.weishitech.qichechangtingyinyue.utils.ThemeManager; import com.weishitech.qichechangtingyinyue.view.FixedSideSnapHelper; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import okhttp3.Call; /** * 高端语音导航首页 */ public class HomeFragment extends BaseFragment { // UI Views private RecyclerView rv, sr_rv, qy_rv, xz_rv; private ImageView iv_ss, iv_gd, sr_start, qy_start, xz_start, iv_bf; private TextView tv_time, tv_song_name; private LinearLayout line; private RelativeLayout relative,relative1,relative2,relative3; // Managers & Helpers private MusicPlayerManager playerManager; private ThemeManager themeManager; private RotationAnimatorHelper rotationHelper; private boolean isUserAuthorizedPlayback = false; // 用户是否已授权播放(点击/滑动) private boolean isMainPlaybackActive = false; // 是否由主流程控制播放状态 // Data & State private SongTopAdapter songTopAdapter; private List<MusicBean.DataBean> musicDataList = new ArrayList<>(); private List<MusicBean.DataBean> privateSongList;//私人播放列表字段 private List<MusicBean.DataBean> qySongList;//轻音乐播放列表字段 private List<MusicBean.DataBean> xzSongList;//小众音乐播放列表字段 private GestureDetector gestureDetector; @Override protected int setLayout() { return R.layout.fragment_home; } @Override protected void initView() { iv_ss = fvbi(R.id.iv_ss); iv_gd = fvbi(R.id.iv_gd); iv_bf = fvbi(R.id.iv_bf); rv = fvbi(R.id.rv); sr_rv = fvbi(R.id.sr_rv); qy_rv = fvbi(R.id.qy_rv); xz_rv = fvbi(R.id.xz_rv); sr_start = fvbi(R.id.sr_start); qy_start = fvbi(R.id.qy_start); xz_start = fvbi(R.id.xz_start); tv_time = fvbi(R.id.tv_time); tv_song_name = fvbi(R.id.tv_song_name); line = fvbi(R.id.line); relative = fvbi(R.id.relative); relative1 = fvbi(R.id.relative1); relative2 = fvbi(R.id.relative2); relative3 = fvbi(R.id.relative3); // 初始化组件 playerManager = new MusicPlayerManager(); themeManager = new ThemeManager(); rotationHelper = new RotationAnimatorHelper(); // 跑马灯 tv_song_name.setEllipsize(TextUtils.TruncateAt.MARQUEE); tv_song_name.setSingleLine(true); tv_song_name.setSelected(true); tv_song_name.setFocusable(true); tv_song_name.setFocusableInTouchMode(true); setupGestureListener(); // 滑动手势换肤 iv_bf.setImageResource(themeManager.getPlayIconRes()); // 默认显示蓝色“播放”图标 } @Override protected void initClick() { // 搜索跳转 iv_ss.setOnClickListener(v -> toClass(SearchActivity.class)); // 播放/暂停按钮 iv_bf.setOnClickListener(v -> { if (musicDataList.isEmpty()) { ToastUtil.showShortToast("暂无歌曲可播放"); return; } if (playerManager.isPlaying() && isMainPlaybackActive) { playerManager.pause(); // 如果暂停了主播放,可以视为退出主控模式(可选) isMainPlaybackActive = false; } else { isMainPlaybackActive = true; // 标记为主控模式 int pos = findBestObscuredItem(rv, DisplayUtils.dp2px(requireContext(), 50f)); if (pos == RecyclerView.NO_POSITION) pos = 0; playMusicAtPosition(pos); } updatePlayButtonIcon(); // 只有主控才更新 iv_bf }); //私人音乐播放按钮点击 sr_start.setOnClickListener(v -> playPrivateFirstSong()); //轻音乐播放按钮点击 qy_start.setOnClickListener(v -> playQingyinFirstSong()); //小众音乐播放按钮点击 xz_start.setOnClickListener(v -> playXiaoZhongFirstSong()); relative1.setOnClickListener(new OnMultiClickListener() { @Override public void onMultiClick(View v) { if (privateSongList == null || privateSongList.isEmpty()) { ToastUtil.showShortToast("暂无数据"); return; } Bundle bundle = new Bundle(); bundle.putString("type", "私人专属好歌"); bundle.putSerializable("song_list", new ArrayList<>(privateSongList)); // 拷贝一份 toClass(MusicListActivity.class, bundle); } }); relative2.setOnClickListener(new OnMultiClickListener() { @Override public void onMultiClick(View v) { if (qySongList == null || qySongList.isEmpty()) { ToastUtil.showShortToast("暂无数据"); return; } Bundle bundle = new Bundle(); bundle.putString("type", "轻音乐"); bundle.putSerializable("song_list", new ArrayList<>(qySongList)); toClass(MusicListActivity.class, bundle); } }); relative3.setOnClickListener(new OnMultiClickListener() { @Override public void onMultiClick(View v) { if (xzSongList == null || xzSongList.isEmpty()) { ToastUtil.showShortToast("暂无数据"); return; } Bundle bundle = new Bundle(); bundle.putString("type", "小众音乐"); bundle.putSerializable("song_list", new ArrayList<>(xzSongList)); toClass(MusicListActivity.class, bundle); } }); } @Override protected void initData() { updateTimeGreeting(); musicAppInfo(); playerManager.setOnPlaybackStateChangedListener(new MusicPlayerManager.OnPlaybackStateChangedListener() { @Override public void onPlaying(int position) { updatePlayButtonIcon(); startRotationForCurrentItem(position); } @Override public void onPaused() { updatePlayButtonIcon(); rotationHelper.stopRotation(); } @Override public void onCompletion() { // 来自主列表 → 播放下一首主歌 if (!playerManager.isPlayingFromPrivate()) { playNextSong(); return; } // 来自外部歌单 → 尝试播放下一首 boolean hasNext = playerManager.playNextInExternalList(); if (!hasNext) { ToastUtil.showShortToast("当前歌单已播放完毕"); updateStartButtonIcons(); // 所有按钮恢复“播放”图标 } } @Override public void onError(String errorMsg) { ToastUtil.showShortToast(errorMsg); updatePlayButtonIcon(); } }); } private void updateTimeGreeting() { Calendar calendar = Calendar.getInstance(); int hour = calendar.get(Calendar.HOUR_OF_DAY); String greeting; if (hour >= 5 && hour < 11) greeting = "HI,早上好"; else if (hour == 11 || hour == 12 || (hour >= 13 && hour < 18)) greeting = "HI,下午好"; else greeting = "HI,晚上好"; tv_time.setText(greeting); } private void musicAppInfo() { HashMap<String, String> map = new HashMap<>(); showDialog(); HttpBuiler.getInfo(Url.music, map).build().execute( new GenericsCallback<MusicBean>(new JsonGenericsSerializator()) { @Override public void onError(Call call, Exception e, int id) { dissmiss(); ToastUtil.showShortToast(e.getMessage()); } @Override public void onResponse(MusicBean response, int id) { dissmiss(); if ("0".equals(response.getCode()) && response.getData() != null && !response.getData().isEmpty()) { musicDataList = response.getData(); setupMusicRecyclerView(); } else { ToastUtil.showShortToast("暂无歌曲数据"); } } }); } private void setupMusicRecyclerView() { songTopAdapter = new SongTopAdapter(); rv.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)); rv.setAdapter(songTopAdapter); // 设置播放状态回调(用于封面旋转) songTopAdapter.setOnPlayStateChangedListener((position, coverView) -> { rotationHelper.stopRotation(); rotationHelper.startRotation(coverView); }); // 必须在设置 adapter 后再初始化滚动逻辑 setupSnappingAndPlayback(rv, songTopAdapter); // 最后设置数据 songTopAdapter.setList(musicDataList); //私人音乐适配器 // 从第三个元素(索引=2)开始,取最多20首歌曲 int start = 2; // 第三个元素的索引 int end = Math.min(start + 20, musicDataList.size()); // 最多取20首,且不能越界 if (start >= musicDataList.size()) { privateSongList = new ArrayList<>(); // 防止越界 ToastUtil.showShortToast("起始位置超出列表范围"); } else { privateSongList = new ArrayList<>(musicDataList.subList(start, end)); } PrivateAdapter privateAdapter = new PrivateAdapter(); // 设置横向 Grid 布局:每行3个 GridLayoutManager gridLayoutManager = new GridLayoutManager(requireContext(), 3, RecyclerView.HORIZONTAL, false); sr_rv.setLayoutManager(gridLayoutManager); sr_rv.setAdapter(privateAdapter); privateAdapter.setList(privateSongList); // <<< 添加吸附效果 >>> new LeftGravitySnapHelper().attachToRecyclerView(sr_rv); //轻音乐 // 确保 musicDataList 不为 null 且有数据 if (musicDataList == null || musicDataList.isEmpty()) { return; } int qystart = 5; int count = 6; int qyend = Math.min(qystart + count, musicDataList.size()); qySongList = new ArrayList<>(musicDataList.subList(qystart, qyend)); QyAdapter qyAdapter = new QyAdapter(); // 设置横向 Grid 布局:每行3个 GridLayoutManager gridLayoutManagers = new GridLayoutManager(requireContext(), 2); qy_rv.setLayoutManager(gridLayoutManagers); qy_rv.setAdapter(qyAdapter); qyAdapter.setList(qySongList); //小众音乐 // 确保 musicDataList 不为 null 且有数据 if (musicDataList == null || musicDataList.isEmpty()) { return; } int xzstart = 8; int xzcount = 6; int xzend = Math.min(xzstart + xzcount, musicDataList.size()); xzSongList = new ArrayList<>(musicDataList.subList(xzstart, xzend)); XzAdapter xzAdapter = new XzAdapter(); // 设置横向 Grid 布局:每行3个 xz_rv.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)); xz_rv.setAdapter(xzAdapter); xzAdapter.setList(xzSongList); } private void setupSnappingAndPlayback(RecyclerView rv, SongTopAdapter adapter) { LinearLayoutManager layoutManager = (LinearLayoutManager) rv.getLayoutManager(); if (layoutManager == null) return; int fixedTargetX = DisplayUtils.dp2px(requireContext(), 50f); FixedSideSnapHelper snapHelper = new FixedSideSnapHelper(fixedTargetX); snapHelper.attachToRecyclerView(rv); AtomicInteger lastPlayedPosition = new AtomicInteger(RecyclerView.NO_POSITION); final boolean[] isFirstLayoutDone = {false}; // <<< 添加标志位 // 延迟标记“首次布局已完成” rv.post(() -> isFirstLayoutDone[0] = true); rv.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); isUserAuthorizedPlayback = true; // 🔥 关键:只有完成首次布局后才允许自动播放 if (!isFirstLayoutDone[0]) { return; } int pos = findBestObscuredItem(recyclerView, fixedTargetX); if (pos != RecyclerView.NO_POSITION && pos != lastPlayedPosition.get()) { playMusicAtPosition(pos); lastPlayedPosition.set(pos); } dispatchVisualFeedback(recyclerView, adapter); } @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); boolean isPlayingBefore = playerManager.isPlaying(); if (newState == RecyclerView.SCROLL_STATE_DRAGGING || newState == RecyclerView.SCROLL_STATE_SETTLING) { if (isPlayingBefore) { playerManager.pause(); rotationHelper.stopRotation(); updatePlayButtonIcon(); } } if (newState == RecyclerView.SCROLL_STATE_IDLE && isPlayingBefore) { playerManager.resume(); startRotationForCurrentItem(playerManager.getCurrentPlayingPosition()); updatePlayButtonIcon(); } } }); setupOneWayScroll(rv, requireContext()); } private int findBestObscuredItem(RecyclerView rv, int fuzzyRightEdge) { LinearLayoutManager lm = (LinearLayoutManager) rv.getLayoutManager(); if (lm == null) return RecyclerView.NO_POSITION; int bestPos = RecyclerView.NO_POSITION; float minDiff = Float.MAX_VALUE; for (int i = 0; i < lm.getChildCount(); i++) { View child = lm.getChildAt(i); if (child == null) continue; int left = lm.getDecoratedLeft(child); int right = lm.getDecoratedRight(child); int width = right - left; if (right <= 0 || left >= fuzzyRightEdge) continue; // 无交集 int overlapStart = Math.max(left, 0); int overlapEnd = Math.min(right, fuzzyRightEdge); int overlapWidth = Math.max(0, overlapEnd - overlapStart); float coveredRatio = (float) overlapWidth / width; float diff = Math.abs(coveredRatio - 0.5f); if (coveredRatio >= 0.3f && coveredRatio <= 0.7f && diff < minDiff) { minDiff = diff; bestPos = lm.getPosition(child); } } return bestPos; } private void playMusicAtPosition(int position) { if (position == RecyclerView.NO_POSITION || position >= musicDataList.size()) return; MusicBean.DataBean bean = musicDataList.get(position); nextSkin(); // 每次播放新歌就换主题 playerManager.play(requireContext(), position, bean.getMusic()); songTopAdapter.updatePlayingPosition(position); tv_song_name.setText(bean.getTitle() + "-" + bean.getSinger()); ToastUtil.showShortToast("播放: " + bean.getTitle()); } private void playNextSong() { int nextPos = playerManager.getCurrentPlayingPosition() + 1; if (nextPos < musicDataList.size()) { playMusicAtPosition(nextPos); scrollToTriggerPosition(nextPos, rv); } else { ToastUtil.showShortToast("已播放完全部歌曲"); } } private void scrollToTriggerPosition(int targetPos, RecyclerView rv) { LinearLayoutManager lm = (LinearLayoutManager) rv.getLayoutManager(); if (lm == null) return; LinearSmoothScroller scroller = new LinearSmoothScroller(requireContext()) { @Override public PointF computeScrollVectorForPosition(int targetPosition) { return lm.computeScrollVectorForPosition(targetPosition); } @Override public int calculateDxToMakeVisible(View view, int snapPreference) { int center = (lm.getDecoratedLeft(view) + lm.getDecoratedRight(view)) / 2; int targetX = DisplayUtils.dp2px(requireContext(), 50f); return targetX - center; } @Override protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { return 150f / displayMetrics.densityDpi; } }; scroller.setTargetPosition(targetPos); lm.startSmoothScroll(scroller); } private void dispatchVisualFeedback(RecyclerView rv, SongTopAdapter adapter) { LinearLayoutManager lm = (LinearLayoutManager) rv.getLayoutManager(); if (lm == null) return; int targetX = DisplayUtils.dp2px(requireContext(), 100f); for (int i = 0; i < rv.getChildCount(); i++) { View child = rv.getChildAt(i); if (child == null) continue; int centerX = (lm.getDecoratedLeft(child) + lm.getDecoratedRight(child)) / 2; float distance = Math.abs(centerX - targetX); if (distance > 600) continue; // float alpha = 1.0f - Math.min(1.0f, distance / 300f); // float scale = 0.8f + 0.2f * (1.0f - Math.min(1.0f, distance / 200f)); // child.setAlpha(alpha); // child.setScaleX(scale); // child.setScaleY(scale); } } private void setupOneWayScroll(RecyclerView rv, Context context) { final boolean[] hasScrolledLeft = {false}; final float[] startX = {0f}; rv.addOnItemTouchListener(new RecyclerView.SimpleOnItemTouchListener() { @Override public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent e) { switch (e.getAction()) { case MotionEvent.ACTION_DOWN: startX[0] = e.getX(); break; case MotionEvent.ACTION_MOVE: float dx = e.getX() - startX[0]; if (dx < -20 && !hasScrolledLeft[0]) { hasScrolledLeft[0] = true; } if (hasScrolledLeft[0] && dx > 15) { Toast.makeText(context, "暂只支持左滑操作", Toast.LENGTH_SHORT).show(); return true; } break; } return false; } }); } private void setupGestureListener() { gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { float diffX = e2.getX() - e1.getX(); if (Math.abs(diffX) > Math.abs(e2.getY() - e1.getY())) { if (Math.abs(diffX) > 100 && Math.abs(velocityX) > 100) { nextSkin(); // 滑动换肤 return true; } } return false; } }); View.OnTouchListener touchListener = (v, event) -> gestureDetector.onTouchEvent(event); line.setOnTouchListener(touchListener); relative.setOnTouchListener(touchListener); } private void nextSkin() { themeManager.nextTheme(); applyCurrentTheme(); ToastUtil.showShortToast("切换到主题 " + (themeManager.getCurrentThemeIndex() + 1)); } private void applyCurrentTheme() { if (!isAdded()) return; // Fragment 已 detach,不执行 boolean isPlaying = playerManager.isPlaying(); themeManager.applyThemeTo(line, relative, iv_bf, isPlaying); } private void updatePlayButtonIcon() { if (!isAdded()) return; // 如果正在播放私人歌单,则主按钮不响应 if (playerManager.isPlayingFromPrivate()) { iv_bf.setImageResource(themeManager.getPlayIconRes()); return; } // 否则:根据实际播放状态更新图标 boolean isPlaying = playerManager.isPlaying(); iv_bf.setImageResource(isPlaying ? themeManager.getPauseIconRes() : themeManager.getPlayIconRes()); } private void startRotationForCurrentItem(int position) { rotationHelper.stopRotation(); // 先停止任何正在进行的动画 RecyclerView.ViewHolder vh = rv.findViewHolderForAdapterPosition(position); if (vh != null) { ImageView coverView = vh.itemView.findViewById(R.id.iv_song_logo); if (coverView != null) { rotationHelper.startRotation(coverView); } } } /** * 更新所有外部播放按钮为“播放”状态 */ private void updateStartButtonIcons() { sr_start.setImageResource(R.mipmap.img_lan_bf); qy_start.setImageResource(R.mipmap.img_lan_bf); xz_start.setImageResource(R.mipmap.img_lan_bf); } /** * 播放私人专属歌单 */ private void playPrivateFirstSong() { if (privateSongList == null || privateSongList.isEmpty()) { ToastUtil.showShortToast("暂无私人歌曲"); return; } boolean isPlaying = playerManager.isPlaying(); boolean isFromThisList = "private".equals(playerManager.getCurrentPlaylistTag()); // 如果正在播放该列表 → 暂停 if (isPlaying && isFromThisList) { playerManager.pause(); sr_start.setImageResource(R.mipmap.img_lan_bf); ToastUtil.showShortToast("已暂停"); int currentIndex = playerManager.getCurrentExternalIndex(); MusicBean.DataBean currentSong = privateSongList.get(currentIndex); tv_song_name.setText(currentSong.getTitle() + "-" + currentSong.getSinger()); return; } // 如果已暂停且属于该列表 → 恢复 if (!isPlaying && isFromThisList) { playerManager.resume(); sr_start.setImageResource(R.mipmap.img_lan_zt); int currentIndex = playerManager.getCurrentExternalIndex(); MusicBean.DataBean currentSong = privateSongList.get(currentIndex); tv_song_name.setText(currentSong.getTitle() + "-" + currentSong.getSinger()); ToastUtil.showShortToast("继续播放: " + currentSong.getTitle()); return; } // 首次播放 → 从 index=0 开始 playerManager.playFromExternalList(requireContext(), privateSongList, 0, "private"); playerManager.setPlayingFromPrivate(true); sr_start.setImageResource(R.mipmap.img_lan_zt); rotationHelper.stopRotation(); MusicBean.DataBean firstSong = privateSongList.get(0); tv_song_name.setText(firstSong.getTitle() + "-" + firstSong.getSinger()); ToastUtil.showShortToast("播放私人歌曲: " + firstSong.getTitle()); } private void playQingyinFirstSong() { if (qySongList == null || qySongList.isEmpty()) { ToastUtil.showShortToast("暂无轻音乐"); return; } boolean isPlaying = playerManager.isPlaying(); boolean isFromThisList = "qingyin".equals(playerManager.getCurrentPlaylistTag()); if (isPlaying && isFromThisList) { playerManager.pause(); qy_start.setImageResource(R.mipmap.img_lan_bf); ToastUtil.showShortToast("已暂停"); int currentIndex = playerManager.getCurrentExternalIndex(); MusicBean.DataBean currentSong = qySongList.get(currentIndex); tv_song_name.setText(currentSong.getTitle() + "-" + currentSong.getSinger()); return; } if (!isPlaying && isFromThisList) { playerManager.resume(); qy_start.setImageResource(R.mipmap.img_lan_zt); int currentIndex = playerManager.getCurrentExternalIndex(); MusicBean.DataBean currentSong = qySongList.get(currentIndex); tv_song_name.setText(currentSong.getTitle() + "-" + currentSong.getSinger()); ToastUtil.showShortToast("继续播放: " + currentSong.getTitle()); return; } playerManager.playFromExternalList(requireContext(), qySongList, 0, "qingyin"); playerManager.setPlayingFromPrivate(true); qy_start.setImageResource(R.mipmap.img_lan_zt); rotationHelper.stopRotation(); MusicBean.DataBean firstSong = qySongList.get(0); tv_song_name.setText(firstSong.getTitle() + "-" + firstSong.getSinger()); ToastUtil.showShortToast("播放轻音乐: " + firstSong.getTitle()); } private void playXiaoZhongFirstSong() { if (xzSongList == null || xzSongList.isEmpty()) { ToastUtil.showShortToast("暂无小众音乐"); return; } boolean isPlaying = playerManager.isPlaying(); boolean isFromThisList = "xiaozhong".equals(playerManager.getCurrentPlaylistTag()); if (isPlaying && isFromThisList) { playerManager.pause(); xz_start.setImageResource(R.mipmap.img_lan_bf); ToastUtil.showShortToast("已暂停"); int currentIndex = playerManager.getCurrentExternalIndex(); MusicBean.DataBean currentSong = xzSongList.get(currentIndex); tv_song_name.setText(currentSong.getTitle() + "-" + currentSong.getSinger()); return; } if (!isPlaying && isFromThisList) { playerManager.resume(); xz_start.setImageResource(R.mipmap.img_lan_zt); int currentIndex = playerManager.getCurrentExternalIndex(); MusicBean.DataBean currentSong = xzSongList.get(currentIndex); tv_song_name.setText(currentSong.getTitle() + "-" + currentSong.getSinger()); ToastUtil.showShortToast("继续播放: " + currentSong.getTitle()); return; } playerManager.playFromExternalList(requireContext(), xzSongList, 0, "xiaozhong"); playerManager.setPlayingFromPrivate(true); xz_start.setImageResource(R.mipmap.img_lan_zt); rotationHelper.stopRotation(); MusicBean.DataBean firstSong = xzSongList.get(0); tv_song_name.setText(firstSong.getTitle() + "-" + firstSong.getSinger()); ToastUtil.showShortToast("播放小众音乐: " + firstSong.getTitle()); } @Override public void onDestroy() { playerManager.release(); rotationHelper.stopRotation(); super.onDestroy(); } } 上面说的那个问题还存在
最新发布
12-04
你反馈的问题: > ✅ 播放私人歌单第1首 → 自动播第2首 > ❌ 点击“暂停”按钮,**没有暂停第二首,而是重新从第一首开始播放** > 🔁 再点一次才真正“暂停” --- ## 🚨 **问题依然存在的根本原因分析** 虽然我们已经用 `tag` 替代了对象引用比较,但还有一个致命漏洞: ### 🔥 你在 `playFromExternalList(...)` 中调用了 `release()`! ```java public void playFromExternalList(...) { release(); // ← 这里清空了所有状态!!! mediaPlayer = new MediaPlayer(); ... } ``` 👉 调用 `release()` 会: - 清空 `currentExternalIndex` - 清空 `currentPlaylistTag` - 清空 `mediaPlayer` - 导致 `isFromThisList` 判断失败 所以即使你正在播放 `"private"` 歌单的第2首,**只要一点击“播放”,就会触发 `playFromExternalList` → 执行 `release()` → 状态丢失 → 从 index=0 开始播放!** --- ## ✅ 最终正确解决方案 ### ✅ 核心原则: > **不要在 resume 或 pause 场景下调用 `playFromExternalList()`!** 只有首次播放时才应该调用它。 如果只是恢复播放,就只调用 `resume()`。 --- ## ✅ 修复方案(两个文件完整修正版) --- ### ✅ 1. `MusicPlayerManager.java`(关键:**不要每次播放都 release**) ```java package com.weishitech.qichechangtingyinyue.utils; import android.content.Context; import android.media.AudioAttributes; import android.media.MediaPlayer; import androidx.annotation.NonNull; import com.weishitech.qichechangtingyinyue.bean.MusicBean; import java.util.List; public class MusicPlayerManager { private MediaPlayer mediaPlayer; private OnPlaybackStateChangedListener listener; private int currentPlayingPosition = -1; // 主列表位置 private boolean isPlayingFromPrivate = false; // 外部播放信息 private List<MusicBean.DataBean> currentExternalList; private String currentPlaylistTag; // "private", "qingyin", "xiaozhong" private int currentExternalIndex = -1; private String currentPlayingUrl = null; public boolean isPlayingFromPrivate() { return isPlayingFromPrivate; } public void setPlayingFromPrivate(boolean flag) { this.isPlayingFromPrivate = flag; } public String getCurrentPlayingMusicUrl() { return currentPlayingUrl; } public int getCurrentExternalIndex() { return currentExternalIndex; } public String getCurrentPlaylistTag() { return currentPlaylistTag; } public interface OnPlaybackStateChangedListener { void onPlaying(int position); void onPaused(); void onCompletion(); void onError(String errorMsg); } public void setOnPlaybackStateChangedListener(OnPlaybackStateChangedListener listener) { this.listener = listener; } // 播放主列表歌曲 public void play(Context context, int position, String musicUrl) { if (position == currentPlayingPosition && mediaPlayer != null && mediaPlayer.isPlaying()) { return; } try { prepareAndPlay(musicUrl, () -> { currentPlayingPosition = position; currentPlayingUrl = musicUrl; isPlayingFromPrivate = false; currentPlaylistTag = null; if (listener != null) listener.onPlaying(position); }); } catch (Exception e) { e.printStackTrace(); if (listener != null) listener.onError("无法播放音乐"); } } /** * 播放外部列表(仅用于首次启动) */ public void playFromExternalList(Context context, @NonNull List<MusicBean.DataBean> list, int startIndex, String tag) { try { boolean isSameList = tag.equals(currentPlaylistTag); int playIndex = isSameList ? currentExternalIndex : startIndex; if (playIndex < 0 || playIndex >= list.size()) return; MusicBean.DataBean bean = list.get(playIndex); prepareAndPlay(bean.getMusic(), () -> { currentPlayingPosition = -1; currentPlayingUrl = bean.getMusic(); isPlayingFromPrivate = true; currentExternalList = list; currentExternalIndex = playIndex; currentPlaylistTag = tag; if (listener != null) listener.onPlaying(-1); }); } catch (Exception e) { e.printStackTrace(); if (listener != null) listener.onError("无法播放音乐"); } } /** * 公共方法:准备并播放(复用 MediaPlayer) */ private void prepareAndPlay(String musicUrl, Runnable onPreparedAction) throws Exception { boolean needNewPlayer = mediaPlayer == null; if (needNewPlayer) { mediaPlayer = new MediaPlayer(); setupListeners(); } else if (musicUrl.equals(currentPlayingUrl) && !mediaPlayer.isPlaying()) { // 已加载过此 URL,直接 start mediaPlayer.start(); onPreparedAction.run(); return; } else { // 不同 URL,重置并重新加载 mediaPlayer.reset(); } mediaPlayer.setDataSource(musicUrl); mediaPlayer.setAudioAttributes(new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .setUsage(AudioAttributes.USAGE_MEDIA) .build()); mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(mp -> { mp.start(); currentPlayingUrl = musicUrl; onPreparedAction.run(); }); } private void setupListeners() { mediaPlayer.setOnCompletionListener(mp -> { if (listener != null) listener.onCompletion(); }); mediaPlayer.setOnErrorListener((mp, what, extra) -> { if (listener != null) listener.onError("播放错误: " + what)); return true; }); } public void pause() { if (mediaPlayer != null && mediaPlayer.isPlaying()) { mediaPlayer.pause(); if (listener != null) listener.onPaused(); } } public void resume() { if (mediaPlayer != null && !mediaPlayer.isPlaying() && currentPlayingUrl != null) { mediaPlayer.start(); if (listener != null) listener.onPlaying(isPlayingFromPrivate ? -1 : currentPlayingPosition); } } public boolean isPlaying() { return mediaPlayer != null && mediaPlayer.isPlaying(); } public int getCurrentPlayingPosition() { return currentPlayingPosition; } public boolean playNextInExternalList() { if (currentExternalList == null || currentExternalIndex < 0) return false; int nextIndex = currentExternalIndex + 1; if (nextIndex >= currentExternalList.size()) return false; MusicBean.DataBean bean = currentExternalList.get(nextIndex); try { prepareAndPlay(bean.getMusic(), () -> { currentPlayingPosition = -1; currentPlayingUrl = bean.getMusic(); currentExternalIndex = nextIndex; isPlayingFromPrivate = true; // tag 不变 if (listener != null) listener.onPlaying(-1); }); return true; } catch (Exception e) { e.printStackTrace(); return false; } } public void release() { if (mediaPlayer != null) { mediaPlayer.release(); mediaPlayer = null; } currentPlayingPosition = -1; currentPlayingUrl = null; currentExternalIndex = -1; currentPlaylistTag = null; currentExternalList = null; } } ``` --- ### ✅ 2. `HomeFragment.java`(关键:判断是否是“同一个列表+已加载”,避免重复播放) #### 修改后的 `playPrivateFirstSong()`: ```java private void playPrivateFirstSong() { if (privateSongList == null || privateSongList.isEmpty()) { ToastUtil.showShortToast("暂无私人歌曲"); return; } boolean isPlaying = playerManager.isPlaying(); boolean isFromThisList = "private".equals(playerManager.getCurrentPlaylistTag()); // 如果正在播放该列表中的任意一首 → 暂停 if (isPlaying && isFromThisList) { playerManager.pause(); sr_start.setImageResource(R.mipmap.img_lan_bf); ToastUtil.showShortToast("已暂停"); int currentIndex = playerManager.getCurrentExternalIndex(); MusicBean.DataBean currentSong = privateSongList.get(currentIndex); tv_song_name.setText(currentSong.getTitle() + "-" + currentSong.getSinger()); return; } // 如果已暂停,且属于该列表 → 恢复(不重新 prepare) if (!isPlaying && isFromThisList) { playerManager.resume(); sr_start.setImageResource(R.mipmap.img_lan_zt); int currentIndex = playerManager.getCurrentExternalIndex(); MusicBean.DataBean currentSong = privateSongList.get(currentIndex); tv_song_name.setText(currentSong.getTitle() + "-" + currentSong.getSinger()); ToastUtil.showShortToast("继续播放: " + currentSong.getTitle()); return; } // 否则:首次播放 or 切换到新列表 → 从 index=0 开始 playerManager.playFromExternalList(requireContext(), privateSongList, 0, "private"); playerManager.setPlayingFromPrivate(true); sr_start.setImageResource(R.mipmap.img_lan_zt); rotationHelper.stopRotation(); MusicBean.DataBean firstSong = privateSongList.get(0); tv_song_name.setText(firstSong.getTitle() + "-" + firstSong.getSinger()); ToastUtil.showShortToast("播放私人歌曲: " + firstSong.getTitle()); } ``` > ✅ `playQingyinFirstSong()` 和 `playXiaoZhongFirstSong()` 逻辑相同,不再重复贴出。 --- ## ✅ 关键改动总结 | 改动 | 说明 | |------|------| | ✅ 移除 `release()` 在每次播放前的调用 | 避免状态丢失 | | ✅ 使用 `mediaPlayer.reset()` 替代重建 | 安全切换音频源 | | ✅ 新增 `prepareAndPlay()` 方法 | 统一处理播放逻辑 | | ✅ 只有在“不同 URL”时才重新 prepare | 提高性能和状态一致性 | --- ## ✅ 行为验证表 | 操作 | 结果 | |------|------| | 第一次点“私人专属” | 播放第1首 ✅ | | 自动播完 → 第2首 | ✅ | | 点击按钮(第2首播放中) | 暂停第2首 ✅ | | 再点一次 | 继续播放第2首 ✅ | | 不再跳回第1首 ❌ | --- ## ✅ 建议补充日志调试(开发阶段) 在 `playFromExternalList()` 开头加: ```java Log.d("MusicPlayer", "playFromExternalList: tag=" + tag + ", currentIndex=" + currentExternalIndex); ``` 在 `resume()` 加: ```java Log.d("MusicPlayer", "resume called, url=" + currentPlayingUrl + ", isPlaying=" + mediaPlayer.isPlaying()); ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值