app-framework学习--Scroller

Scroller

这个插件允许你创建一个可滚动区域。我们使用的JavaScript滚轮,除非该设备支持 - WebKit的溢出卷轴:触摸。它有许多修复Android版<3和iOS原生的滚动。

创建:

2
$(selector).scroller({}) //create
$(selector).scroller() //get the scroller object

属性:

Attributes

1
2
3
4
5
6
7
8
9
10
11
scrollbars                   (bool) ID of DOM elemenet for the popup container
verticalScroll               (bool) 允许vertical scrolling
horizontalScroll             (bool) 允许horizontal scrolling
useJsScroll                  (bool) 是否允许 JavaScript scroller
lockBounce                   (bool) Prevent the rubber band effect
autoEnable                   (bool) 自动启用滚动条
refresh                      (bool) 上拉刷新
infinite                     (bool) 启用无限滚动
initScrollProgress           (bool) Dispatch progress on touch move
vScrollCSS                   (string) 垂直滚动条
hScrollCSS                   (string) 水平滚动条

方法

1
2
3
4
5
6
7
8
9
10
11
enable()                     启用滚动条
disable()                    禁用滚动条
scrollToBottom(time)         滚动到内容的底部
scrollToTop(time)            滚动到内容顶部
scrollTo(obj,time)           to X / Y 坐标
scrollBy(obj,time)           by X / Y 坐标
addPullToRefresh()           启用下拉刷新的滚动条
setRefreshContent(string)    设置了下拉刷新内容的文字
addInfinite()               addInfinite事件
clearInfinite()              Clear inifinite-scroll-end event
scrollToItem(DOMNode,time)   滚动到屏幕上的特定元素

事件

Events must be registered on the scroller using $.bind()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//scroller object events
scrollstart                  Scrolling started
scroll                       Scrolling progress
scrollend                    Scrolling stopped
 
//pull to refresh
refresh-trigger              Pull to refresh scroll started
refresh-release              Event when pull to refresh is has happened
refresh-cancel               User cancelled pull to refresh by scrolling
refresh-finish               Pull to refresh has finished and hidden
 
//infinite scroll
infinite-scroll              User scrolled to the bottom of the content
infinite-scroll-end          User finished scrolling

CSS/Customize

Below is an example used by App Framework's iOS7 theme to customize the look and feel of the popup

1
2
3
4
5
6
7
8
9
.scrollBar {
     position: absolute ;
     width: 5px !important;
     height: 20px !important;
     border-radius: 2px !important;
     border: 1px solid black !important;
     background: red !important;
     opacity: 0 !important;
}

Examples

 在HTML 中添加

1
||div id= "scroll" style= 'width:100%;height:200;' ></div>

js中创建

1
2
3
4
5
var myScroller=$( "#scroll" ).scroller({
    verticalScroll: true ,
    horizontalScroll: false ,
    autoEnable: true
})

调用方法

1
myScroller.addPullToRefresh();

从缓存中获取滚动条

1
var myScroller=$( "#scroll" ).scroller(); //no parameters

Pull to refresh

下面是如何结合事件和执行下拉刷新的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
myScroller.addPullToRefresh();
 
//User is dragging the page down exposing the pull to refresh message.
$.bind(myScroller, "refresh-trigger" , function () {
     console.log( "Refresh trigger" );
});
 
//Here we listen for the user to pull the page down and then let go to start the pull to refresh callbacks.
var hideClose;
$.bind(myScroller, "refresh-release" , function () {
     var that = this ;
     console.log( "Refresh release" );
     clearTimeout(hideClose);
     //For the demo, we set a timeout of 5 seconds to show how to hide it asynchronously
     hideClose = setTimeout( function () {
         console.log( "hiding manually refresh" );
         that.hideRefresh();
     }, 5000);
     return false ; //tells it to not auto-cancel the refresh
});
 
//This event is triggered when the user has scrolled past and the pull to refresh block is no longer available
$.bind(myScroller, "refresh-cancel" , function () {
     clearTimeout(hideClose);
     console.log( "cancelled" );
});

infinite scrolling

The following shows how to implement infinite scrolling.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
myScroller.addInfinite();
 
//Bind the infinite scroll event
$.bind(myScroller, "infinite-scroll" , function () {
     var self = this ;
     console.log( "infinite triggered" );
     //Append text at the bottom
     $( this .el).append( "
 
<div id=" infinite " style=" border:2px solid black;margin-top:10px;width:100%;height:20px ">Fetching content...</div>
 
" );
     //Register for the infinite-scroll-end - this is so we do not get it multiple times, or a false report while infinite-scroll is being triggered;
     $.bind(myScroller, "infinite-scroll-end" , function () {
         //unbind the event since we are handling it
         $.unbind(myScroller, "infinite-scroll-end" );
         self.scrollToBottom();
         //Example to show how it could work asynchronously
         setTimeout( function () {
             $(self.el).find( "#infinite" ).remove();
             //We must call clearInfinite() when we are done to reset internal variables;
             self.clearInfinite();
             $(self.el).append( "
 
<div>This was loaded via inifinite scroll<br>More Content</div>
 
" );
             self.scrollToBottom();
         }, 3000);
     });
});
有什么问题可以联系我

官网链接:http://app-framework-software.intel.com/api.php#scroller

欢迎加入学习交流群:333492644

package com.weishitech.qichechangtingyinyue.fragment.home; import android.annotation.SuppressLint; 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.util.Log; 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.fragment.app.Fragment; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearSmoothScroller; import androidx.recyclerview.widget.RecyclerView; import com.chad.library.adapter.base.BaseQuickAdapter; import com.chad.library.adapter.base.listener.OnItemClickListener; 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.MainActivity; import com.weishitech.qichechangtingyinyue.R; import com.weishitech.qichechangtingyinyue.bean.MusicBean; import com.weishitech.qichechangtingyinyue.bean.MusicCache; import com.weishitech.qichechangtingyinyue.bean.NavigationSource; 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.fragment.lilv.LiLvFragment; 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 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; private PrivateAdapter privateAdapter; private MainActivity mActivity; @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 = MusicPlayerManager.getInstance(); //单例获取 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()); // 默认显示蓝色“播放”图标 if (NavigationSource.isDirectClick()) { NavigationSource.clear(); // 清理,防止影响下一次 } mActivity = (MainActivity) getActivity(); } @Override protected void initClick() { // 搜索跳转 iv_ss.setOnClickListener(v -> { Bundle bundle = new Bundle(); // 添加私人歌曲列表 if (privateSongList != null && !privateSongList.isEmpty()) { bundle.putSerializable("private_song_list", new ArrayList<>(privateSongList)); } else { bundle.putSerializable("private_song_list", new ArrayList<MusicBean.DataBean>()); } // 添加轻音乐列表 if (qySongList != null && !qySongList.isEmpty()) { bundle.putSerializable("qingyin_song_list", new ArrayList<>(qySongList)); } else { bundle.putSerializable("qingyin_song_list", new ArrayList<MusicBean.DataBean>()); } // 跳转并传参 toClass(SearchActivity.class, bundle); }); iv_gd.setOnClickListener(new OnMultiClickListener() { @Override public void onMultiClick(View v) { mActivity.JumpToPrice(); } }); // 播放/暂停按钮 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 }); relative1.setOnClickListener(new OnMultiClickListener() { @Override public void onMultiClick(View v) { if (privateSongList == null || privateSongList.isEmpty()) { ToastUtil.showShortToast("暂无数据"); return; } // 标记为「非直接来源」 NavigationSource.setSource(NavigationSource.FROM_MUSIC_LIST); 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; } // 标记为「非直接来源」 NavigationSource.setSource(NavigationSource.FROM_MUSIC_LIST); 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; } // 标记为「非直接来源」 NavigationSource.setSource(NavigationSource.FROM_MUSIC_LIST); 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) { if (!playerManager.isPlayingFromPrivate()) { updatePlayButtonIcon(); } else { String tag = playerManager.getCurrentPlaylistTag(); if ("private".equals(tag)) { int idx = playerManager.getCurrentExternalIndex(); privateAdapter.updatePlayingPosition(idx, true); } } startRotationForCurrentItem(position); } @Override public void onPaused() { if (!playerManager.isPlayingFromPrivate()) { updatePlayButtonIcon(); } else { String tag = playerManager.getCurrentPlaylistTag(); if ("private".equals(tag)) { int idx = playerManager.getCurrentExternalIndex(); privateAdapter.updatePlayingPosition(idx, false); } } rotationHelper.stopRotation(); } @Override public void onCompletion() { String currentTag = playerManager.getCurrentPlaylistTag(); Log.d("HomeFragment", "onCompletion: 当前播放来源 = " + currentTag); Log.d("HomeFragment", "onCompletion: 开始播放下一首"); if ("private".equals(currentTag)) { // 正在播放私人列表 boolean hasNext = playerManager.playNextInExternalList(); if (hasNext) { int nextIdx = playerManager.getCurrentExternalIndex(); privateAdapter.updatePlayingPosition(nextIdx, true); } else { // 循环播放第一首 playerManager.playFromExternalList(requireContext(), privateSongList, 0, "private"); privateAdapter.updatePlayingPosition(0, true); } } // 其他 tag 可扩展(如 qingyin、xiaozhong) else { Log.d("HomeFragment", "onCompletion: 即将调用 playNextSong()"); // 默认行为:播放主列表下一首 playNextSong(); } rotationHelper.stopRotation(); } @Override public void onError(String errorMsg) { ToastUtil.showShortToast(errorMsg); if (playerManager.isPlayingFromPrivate()) { privateAdapter.clearPlayingState(); } else { updatePlayButtonIcon(); } rotationHelper.stopRotation(); } }); } 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(); // 缓存主歌单 MusicCache.setMainMusicList(musicDataList); setupMusicRecyclerView(); notifyLiLvFragmentRefresh(); } 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); //点击top条目跳转到歌词页面 songTopAdapter.setOnItemClickListener((position, dataBean) -> { // 标记为「直接点击」来源 NavigationSource.setSource(NavigationSource.DIRECT_CLICK); // 创建 Bundle 传递数据 Bundle bundle = new Bundle(); bundle.putString("title", dataBean.getTitle()); bundle.putString("singer", dataBean.getSinger()); bundle.putString("cover_url", dataBean.getCover()); bundle.putString("music_url", dataBean.getMusic()); bundle.putString("time", dataBean.getSongTime()); bundle.putInt("position", position); bundle.putInt("total_count", musicDataList.size()); // 跳转到 LyricsActivity toClass(LyricsActivity.class, bundle); }); //私人音乐适配器 // 从第三个元素(索引=2)开始,取最多20首歌曲 int start = 3; // 第三个元素的索引 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 = new PrivateAdapter(); privateAdapter.setMainPlayButtonUpdater(isPlaying -> { Log.d("11111111",""+isPlaying); iv_bf.setImageResource(isPlaying ? themeManager.getPauseIconRes() : themeManager.getPlayIconRes()); }); // 设置点击事件:点击 item 播放该歌曲 // privateAdapter.setOnItemClickListener((position, dataBean) -> { // playerManager.playFromExternalList(requireContext(), privateSongList, position, "private"); // playerManager.setPlayingFromPrivate(true); // ToastUtil.showShortToast("播放: " + dataBean.getTitle()); // }); // 设置横向 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); privateAdapter.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(@NonNull BaseQuickAdapter<?, ?> adapter, @NonNull View view, int position) { // 创建 Bundle 传递数据 Bundle bundle = new Bundle(); bundle.putString("title", privateSongList.get(position).getTitle()); bundle.putString("singer", privateSongList.get(position).getSinger()); bundle.putString("cover_url", privateSongList.get(position).getCover()); bundle.putString("music_url", privateSongList.get(position).getMusic()); bundle.putString("time", privateSongList.get(position).getSongTime()); bundle.putInt("position", position); bundle.putInt("total_count", privateSongList.size()); // 跳转到 LyricsActivity toClass(LyricsActivity.class, bundle); } }); //轻音乐 // 确保 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); qyAdapter.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(@NonNull BaseQuickAdapter<?, ?> adapter, @NonNull View view, int position) { // 创建 Bundle 传递数据 Bundle bundle = new Bundle(); bundle.putString("title", qySongList.get(position).getTitle()); bundle.putString("singer", qySongList.get(position).getSinger()); bundle.putString("cover_url", qySongList.get(position).getCover()); bundle.putString("music_url", qySongList.get(position).getMusic()); bundle.putString("time", qySongList.get(position).getSongTime()); bundle.putInt("position", position); bundle.putInt("total_count", qySongList.size()); // 跳转到 LyricsActivity toClass(LyricsActivity.class, bundle); } }); //小众音乐 // 确保 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); // <<< 添加吸附效果 >>> new LeftGravitySnapHelper().attachToRecyclerView(xz_rv); xzAdapter.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(@NonNull BaseQuickAdapter<?, ?> adapter, @NonNull View view, int position) { // 创建 Bundle 传递数据 Bundle bundle = new Bundle(); bundle.putString("title", xzSongList.get(position).getTitle()); bundle.putString("singer", xzSongList.get(position).getSinger()); bundle.putString("cover_url", xzSongList.get(position).getCover()); bundle.putString("music_url", xzSongList.get(position).getMusic()); bundle.putString("time", xzSongList.get(position).getSongTime()); bundle.putInt("position", position); bundle.putInt("total_count", xzSongList.size()); // 跳转到 LyricsActivity toClass(LyricsActivity.class, bundle); } }); } 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); // 关键:只有完成首次布局后才允许自动播放 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; } @SuppressLint("SetTextI18n") 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()); //使用 post() 延迟更新 adapter,避免在 layout 过程中修改 rv.post(() -> { songTopAdapter.updatePlayingPosition(position); // 同时更新底部显示 tv_song_name.setText(bean.getTitle() + "-" + bean.getSinger()); }); } private void playNextSong() { int currentPos = playerManager.getCurrentPlayingPosition(); int nextPos = currentPos + 1; if (nextPos >= musicDataList.size()) { ToastUtil.showShortToast("已播放完全部歌曲"); updatePlayButtonIcon(); // 确保按钮变回“播放” return; } Log.d("HomeFragment", "playNextSong: currentPos=" + currentPos + ", nextPos=" + nextPos); // 自动播放下一首 playMusicAtPosition(nextPos); // 滚动 RecyclerView 到目标位置 scrollToTriggerPosition(nextPos, rv); } 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 left = lm.getDecoratedLeft(view); int targetX = DisplayUtils.dp2px(requireContext(), 50f); // 目标左边缘 return targetX - left; // 让 view.left 移动到 targetX } @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; // 只负责换肤:背景等 themeManager.applyThemeTo(line, relative, null, false); // 单独设置播放按钮图标,使用动态资源 iv_bf.setImageResource(playerManager.isPlaying() ? themeManager.getPauseIconRes() : themeManager.getPlayIconRes()); } /** * 更新主播放按钮图标(根据实际播放状态) */ private void updatePlayButtonIcon() { if (!isAdded()) return; boolean isPlaying = playerManager.isPlaying(); Log.d("HomeFragment", "🔄 updatePlayButtonIcon: isPlaying=" + isPlaying + ", currentTheme=" + themeManager.getCurrentThemeIndex()); iv_bf.setImageResource(isPlaying ? themeManager.getPauseIconRes() : themeManager.getPlayIconRes()); } private void startRotationForCurrentItem(int position) { // 先停止任何正在进行的动画 rotationHelper.stopRotation(); // 获取 ViewHolder RecyclerView.ViewHolder vh = rv.findViewHolderForAdapterPosition(position); if (vh == null) { // item 当前不可见,可以延迟执行或跳过 return; } ImageView coverView = vh.itemView.findViewById(R.id.iv_song_logo); if (coverView != null) { rotationHelper.startRotation(coverView); // 开始旋转 } } @Override public void onDestroy() { playerManager.release(); rotationHelper.stopRotation(); super.onDestroy(); } @Override public void onResume() { super.onResume(); Log.d("HomeFragment", "onResume called, source = " + NavigationSource.getSource()); // 只有是从「直接点击」进入 LyricsActivity 再返回,才执行以下逻辑 if (NavigationSource.isDirectClick()) { updatePlayButtonIcon(); applyCurrentTheme(); if (playerManager.isPlaying() && !playerManager.isPlayingFromPrivate()) { int currentPos = playerManager.getCurrentPlayingPosition(); syncToCurrentPlayingItem(currentPos); } } else { // 其他路径返回,不执行自动滚动和高亮 updatePlayButtonIcon(); // 至少更新按钮状态 // 可选:清除标记,避免下次误判 NavigationSource.clear(); // Log.d("HomeFragment", "非直接路径返回,跳过 syncToCurrentPlayingItem"); } } /** * 滚动并高亮当前正在播放的歌曲 */ private void syncToCurrentPlayingItem(int position) { if (songTopAdapter == null || rv == null || musicDataList == null || position >= musicDataList.size()) { return; } LinearLayoutManager layoutManager = (LinearLayoutManager) rv.getLayoutManager(); if (layoutManager == null) return; // 目标 x 坐标:距离左边 50dp int targetX = DisplayUtils.dp2px(requireContext(), 50f); LinearSmoothScroller scroller = new LinearSmoothScroller(requireContext()) { @Override public PointF computeScrollVectorForPosition(int targetPosition) { return layoutManager.computeScrollVectorForPosition(targetPosition); } @Override public int calculateDxToMakeVisible(View view, int snapPreference) { // 正确调用:通过 layoutManager 调用 int left = layoutManager.getDecoratedLeft(view); int right = layoutManager.getDecoratedRight(view); int center = (left + right) / 2; // 返回需要移动的距离 return targetX - center; } @Override protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { // 控制滚动速度:越小越快 return 100f / displayMetrics.densityDpi; // 可调 } }; scroller.setTargetPosition(position); layoutManager.startSmoothScroll(scroller); // 延迟查找 ViewHolder 并启动旋转 rv.postDelayed(() -> { RecyclerView.ViewHolder vh = rv.findViewHolderForAdapterPosition(position); if (vh != null) { ImageView coverView = vh.itemView.findViewById(R.id.iv_song_logo); if (coverView != null) { rotationHelper.stopRotation(); rotationHelper.startRotation(coverView); } } }, 350); // 等待滚动接近完成 } private void notifyLiLvFragmentRefresh() { // 假设你通过某种方式获取到 LiLvFragment 实例 // 方法一:通过 Activity 管理 Fragment lilvFragment = getParentFragmentManager().findFragmentByTag("LILV_FRAGMENT"); if (lilvFragment instanceof LiLvFragment) { ((LiLvFragment) lilvFragment).refreshData(); } } } 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 static volatile MusicPlayerManager instance; // 双检锁 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; // 私有构造 private MusicPlayerManager() { // 不再传 context,MediaPlayer 不依赖 Activity Context } // 获取单例 public static MusicPlayerManager getInstance() { if (instance == null) { synchronized (MusicPlayerManager.class) { if (instance == null) { instance = new MusicPlayerManager(); } } } return instance; } // 提供对外接口... 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 = "main"; // <<< 关键:设置为 "main" 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 { if (startIndex < 0 || startIndex >= list.size()) { throw new IndexOutOfBoundsException("Invalid start index: " + startIndex); } MusicBean.DataBean bean = list.get(startIndex); String newUrl = bean.getMusic(); // 关键:无论是否是同一个列表,只要 URL 不同就重新播放 if (mediaPlayer != null && mediaPlayer.isPlaying() && newUrl.equals(currentPlayingUrl)) { // 同一首歌 → 只更新元数据,不重播 currentExternalList = list; currentExternalIndex = startIndex; currentPlaylistTag = tag; isPlayingFromPrivate = true; if (listener != null) listener.onPlaying(-1); } else { // 不同歌曲 or 未播放 → 重置并准备新音频 prepareAndPlay(newUrl, () -> { currentPlayingPosition = -1; // 不属于主列表 currentPlayingUrl = newUrl; isPlayingFromPrivate = true; currentExternalList = list; currentExternalIndex = startIndex; currentPlaylistTag = tag; if (listener != null) listener.onPlaying(-1); }); } } catch (Exception e) { e.printStackTrace(); if (listener != null) listener.onError("无法播放音乐: " + e.getMessage()); } } /** * 公共方法:准备并播放(复用 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(); setupListeners(); //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) % currentExternalList.size(); // 循环 MusicBean.DataBean bean = currentExternalList.get(nextIndex); try { prepareAndPlay(bean.getMusic(), () -> { currentPlayingPosition = -1; currentPlayingUrl = bean.getMusic(); currentExternalIndex = nextIndex; isPlayingFromPrivate = true; 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; } // 新增:获取当前播放时间 public int getCurrentPosition() { if (mediaPlayer != null) { return mediaPlayer.getCurrentPosition(); } return 0; } // 新增:获取总时长 public int getDuration() { if (mediaPlayer != null) { return mediaPlayer.getDuration(); } return 0; } // 新增:跳转到指定位置 public void seekTo(int milliseconds) { if (mediaPlayer != null && mediaPlayer.isPlaying()) { mediaPlayer.seekTo(milliseconds); } } } package com.weishitech.qichechangtingyinyue.utils; import android.view.View; import android.widget.ImageView; import com.weishitech.qichechangtingyinyue.R; public class ThemeManager { public static final int THEME_BLUE = 0; public static final int THEME_GREEN = 1; public static final int THEME_RED = 2; private int[] backgrounds = { R.mipmap.img_lan_bg, R.mipmap.img_lv_bg, R.mipmap.img_hong_bg }; private int[] playIcons = { R.mipmap.img_lan_bf, R.mipmap.img_lv_bf, R.mipmap.img_hong_bf }; private int[] pauseIcons = { R.mipmap.img_lan_zt, R.mipmap.img_lv_zt, R.mipmap.img_hong_zt }; private int currentThemeIndex = 0; /** * 下一个主题(循环) */ public void nextTheme() { currentThemeIndex = (currentThemeIndex + 1) % backgrounds.length; } /** * 设置指定主题 */ public void setTheme(int index) { if (index >= 0 && index < backgrounds.length) { currentThemeIndex = index; } } /** * 应用当前主题到视图 */ public void applyThemeTo(View backgroundView1, View backgroundView2, ImageView iconView, boolean isPlaying) { try { // 更新背景 if (backgroundView1 != null) { backgroundView1.setBackgroundResource(backgrounds[currentThemeIndex]); } if (backgroundView2 != null) { backgroundView2.setBackgroundResource(backgrounds[currentThemeIndex]); } // 更新图标 if (iconView != null) { int resId = isPlaying ? pauseIcons[currentThemeIndex] : playIcons[currentThemeIndex]; iconView.setImageResource(resId); } } catch (Exception e) { e.printStackTrace(); } } public int getCurrentThemeIndex() { return currentThemeIndex; } public int getBackgroundRes() { return backgrounds[currentThemeIndex]; } public int getPauseIconRes() { return pauseIcons[currentThemeIndex]; } public int getPlayIconRes() { return playIcons[currentThemeIndex]; } } 滑动列表播放,播放完第一首,没有自动滑动到下一个歌曲播放没有走public void onCompletion()方法
最新发布
12-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值