RecyclerView不回调RecyclerView.SCROLL_STATE_IDLE的问题

本文详细探讨了RecyclerView在不同滑动状态下的行为特点,并针对滑动到边界时不触发SCROLL_STATE_IDLE状态的问题提供了两种有效解决方案。

圣者无名,大者无形。
鹰立如睡,虎行似病。


1. RecyclerView的滑动状态

RecyclerView在我们日常开发中,是必不可少的"神器"。有时候我们需要对其滚动过程进行监听,从而进行相应的一些操作。
常规操作:

 mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
               // TODO 根据『newState』 to do sth...
            }
 }

关于RecycleView滑动状态:

 	/**
 	 * 当前RecyclerView静止(当前不在滚动状态)
     * The RecyclerView is not currently scrolling.
     */
    public static final int SCROLL_STATE_IDLE = 0;

    /**
     * RecyclerView当前被拖拽滚动(简而言之,手指没有离开屏幕) 
     * The RecyclerView is currently being dragged by outside input such as user touch input.
     */
    public static final int SCROLL_STATE_DRAGGING = 1;

    /**
     * RecyclerView当前正在设置动画到最终位置,不受外部控制(即:惯性滑动过程中)
     * The RecyclerView is currently animating to a final position while not under
     * outside control.
     */
    public static final int SCROLL_STATE_SETTLING = 2;

2.遇到的问题

RecyclerView 只有滑动到中间位置会回调SCROLL_STATE_IDLE,而正常滑动到左右两边却不会触发。(PS:滑动到两边如果我们滑动幅度较大触发回弹效果,就能触发SCROLL_STATE_IDLE

以下列举网上关于这种情况的讨论:

  1. RecyclerView SCROLL_STATE_IDLE is being called late

  2. SCROLL_STATE_IDLE is never called in case of scrolling up #24

  3. ListView does not report SCROLL_STATE_IDLE after SCROLL_STATE_TOUCH_SCROLL


3. 解决的思路

3.1. 思路正确,但解决不了问题

正常来讲,大多数人都很容易想到的方案,那就是在状态为SCROLL_STATE_SETTLING时,强制其停止,即直接调用stopScroll(),让其停止。(PS: 理想很丰满…

3.2 完美解决方案

答案就在这里:真正能解决问题的链接

对于不能科学上网的同学,这里将两种方案搬到这里:

Answer 1

这里的高明之处在于: first + count > mListAdapter.getCount(),用来判断是否在边界位置。

mList.setOnScrollListener(new OnScrollListener() {

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        mListAdapter.setIsScrolling(scrollState != SCROLL_STATE_IDLE);
        Log.i(this, "scrollStateChanged" + scrollState);

        int first = view.getFirstVisiblePosition();
        int count = view.getChildCount();

        if (scrollState == SCROLL_STATE_IDLE || (first + count > mListAdapter.getCount()) ) {
            mList.invalidateViews();
        }
    }
});
Answer 2

核心思想都是 想办法判断边界位置,这里提供了一种区别于Answer1的方法。具体就参考下面代码即可。

recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {

    override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
        super.onScrollStateChanged(recyclerView, newState)

        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            // TODO sth
        }
    }

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)

        /* If the user scrolls to the edges of the recyclerview, we can't trust that we get the SCROLL_STATE_IDLE state.
         * Therefore we have to update the view here in onScrolled for these cases
         */
        if (!recyclerView.canScrollVertically(1) || !recyclerView.canScrollVertically(-1)) {
            // TODO sth
        }
    }
}

希望大家别遇到这个问题…

import android.content.Context; import android.graphics.PointF; 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.SongTopAdapter; import com.weishitech.qichechangtingyinyue.utils.DisplayUtils; import com.weishitech.qichechangtingyinyue.utils.MusicPlayerManager; 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; // Managers & Helpers private MusicPlayerManager playerManager; private ThemeManager themeManager; private RotationAnimatorHelper rotationHelper; // Data & State private SongTopAdapter songTopAdapter; private List<MusicBean.DataBean> musicDataList = new ArrayList<>(); 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); // 初始化组件 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 (playerManager.isPlaying()) { playerManager.pause(); } else { playerManager.resume(); } updatePlayButtonIcon(); // 根据状态更新图标 }); } @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() { playNextSong(); } @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(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); //私人音乐适配器 PrivateAdapter privateAdapter = new PrivateAdapter(); sr_rv.setLayoutManager(new GridLayoutManager(requireContext(),3, GridLayoutManager.HORIZONTAL,false)); sr_rv.setAdapter(privateAdapter); privateAdapter.setList(musicDataList); } 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); rv.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); 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; themeManager.applyThemeTo(null, null, iv_bf, playerManager.isPlaying()); } 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); } } } @Override public void onDestroy() { playerManager.release(); rotationHelper.stopRotation(); super.onDestroy(); } }完整输出这段代码
最新发布
12-04
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值