当前位置: 首页 > 知识库问答 >
问题:

在循环视图中拖动项目时自动滚动滚动条

长孙诚
2023-03-14

我有一个问题与自动滚动在滚动视图。

在我的例子中,有两个Recyclerview。第一个循环视图,水平滚动,第二个垂直滚动。第一个循环视图仅用于拖动,第二个循环视图只用于拖放。两个循环视图都在ScrollView中,所以我在第二个循环视图中禁用了垂直滚动。我在第二个Recyclerview的项目中添加了DragListener。每个项目都有一个拖动侦听器,因此我在拖放项目时添加/替换项目。

所以我的主要问题是滚动,所以当我拖动项目时滚动不能正常工作。目前我使用下面的代码在拖动时滚动。

case DragEvent.ACTION_DRAG_LOCATION:
RecyclerView recyclerView = (RecyclerView) viewSource.getParent();
MyAdapter adapter = (MyAdapter) recyclerView.getAdapter();
int y = Math.round(dragEvent.getY());
Timber.d("onDrag(): " + y);
int translatedY = y - adapter.getScrollDistance();
Timber.d("onDrag(): translated : " + translatedY + "   getScrollDistance : " + adapter.getScrollDistance());
int threshold = 50;
// make a scrolling up due the y has passed the threshold
if (translatedY < threshold) {
    // make a scroll up by 30 px
     mScrollView.smoothScrollBy(0, -30);

} else
    // make a autoscrolling down due y has passed the 500 px border
    if (translatedY + threshold > 500) {
        // make a scroll down by 30 px
     mScrollView.smoothScrollBy(0, 30);
    }
break;

但是,如果recyclerview只有一个项目超过其正常工作范围,则上述代码对于多个项目不能正常工作。但是,当recyclerview有多个项目时,时间滚动视图在两个项目之间的同时在上下滑动。

编辑问题:现在,我把上面的代码放在第二个回收器视图的OnDragListener上。现在拖动侦听器的问题,所以我希望如果用户将First Recyclerview的项目拖动到下面/上面,而不是第二个Recyclerview的拖动侦听器需要工作,否则第二个Recrickerview的项目的拖动侦听器需要工作。

共有2个答案

卢志业
2023-03-14

我创建了一个助手类,使得在RecyclerViews的拖放中添加滚动更加容易。

它是用C#为Xamarin编写的,但将其更改为Java或Kotlin应该很简单。

回收器视图DragScrollHelper在GitHub上

using System;
using System.Threading.Tasks;
using Android.Graphics;
using Android.OS;
using Android.Support.V7.Widget;
using Android.Views;

namespace SharedLibrary.Droid.Helpers {
    public class RecyclerViewDragScrollHelper {
        DragEvent dragEvent = null; //For passing the drag event to newly bound views
        Handler handler = new Handler(); //to pass the run things in the ui thread
        ScrollDirection currentScrollDirection = ScrollDirection.None; //the current direction being scrolled

        /// <summary>
        /// If this is not set then it will try to find a recycler view
        /// in the hovered over view's view tree
        /// </summary>
        public RecyclerView RecyclerViewToScroll { get; set; }

        public RecyclerViewDragScrollHelper() {

        }

        public RecyclerViewDragScrollHelper(RecyclerView recyclerViewToScroll) {
            RecyclerViewToScroll = recyclerViewToScroll;
        }


        public enum UnitType {
            Pixels,
            Percent
        }


        enum ScrollDirection {
            None,
            Foward,
            Back
        }

        /// <summary>
        /// Threshold value can be a percentage or pixels
        /// </summary>
        public UnitType ThresholdUnits { get; set; } = UnitType.Percent;

        /// <summary>
        /// Threshold of how many pixels or percentage from the edge it has to be before it 
        /// starts scrolling
        /// </summary>
        public float Threshold { get; set; } = 0.20f;

        /// <summary>
        /// Speed of the scroll
        /// </summary>
        public int ScrollInterval { get; set; } = 5;


        /// <summary>
        /// Place this method in the method for event handler for View.Drag
        /// Or if useing View.SetOnDragListener(), in the implimentation of
        /// View.IOnDragListener
        /// </summary>
        /// <param name="view"></param>
        /// <param name="dragEvent"></param>
        /// <returns></returns>
        public void HandleDrag(View view, DragEvent dragEvent) {
            var action = dragEvent.Action;
            switch (action) {
                case DragAction.Started:
                    DragStarted(dragEvent);
                    break;
                case DragAction.Ended:
                    DragEnded();
                    break;
                case DragAction.Location:
                    var dragpoint = new Point(Convert.ToInt32(dragEvent.GetX()), Convert.ToInt32(dragEvent.GetY()));
                    HandleLocation(view, dragpoint);
                    break;
                case DragAction.Exited:
                    currentScrollDirection = ScrollDirection.None;
                    break;
            }
        }



        /// <summary>
        /// Run on DragAction.Location.
        /// If it's within the threshold bounds it should scroll the recycler view.
        /// Should work for both vertical and horizontal linear layouts.
        /// Wont currently work for other layouts
        /// </summary>
        /// <param name="viewHoveredOver">This is the view that is being dragged over.
        /// It can be a view in a recyclerview, or the recycler view it's self</param>
        /// <param name="position"></param>
        /// <returns></returns>
        private void HandleLocation(View viewHoveredOver, Point position) {
            var recyclerView = RecyclerViewToScroll;
            var viewHoveredOverIsChildOfRecyclerView = false;
            //if RecyclerViewToScroll has not been set then
            //see if there is a recycler view in the viewHoveredOver view tree
            if (recyclerView == null) {
                recyclerView = FindRecyclerViewInTree(viewHoveredOver);
                viewHoveredOverIsChildOfRecyclerView = true;
            } else {
                viewHoveredOverIsChildOfRecyclerView = IsViewChildOfView(viewHoveredOver, recyclerView);
            }

            if (recyclerView != null &&
                    viewHoveredOverIsChildOfRecyclerView
                    && recyclerView.GetLayoutManager() is LinearLayoutManager layoutManager) {
                //get the drag position in the view, and work out where it is
                //relative to the recycler view. (Both viewHoveredOver and recyclerView
                //might be the same view, this doesn't matter)
                var pointInRecyclerView = GetPointRelativeTo(viewHoveredOver, recyclerView, position);

                //work out the properties for the scrolling based on if it's 
                //verticle or horizontal
                float offSet = 0; //the x or y coord for the drag depending on the orientaion
                float upperThreshold = 0; //threshold postion at the end of the recyclerview
                int scrollX = 0; //the absolute amount to scroll horizontally
                int scrollY = 0; //the absolute amount to scroll vertically
                var layoutDirection = layoutManager.Orientation;

                //If the threshold units are pixels then just take the threshold value.
                //If it's percentage times the threshold by the height to get the pixels
                var thresholdPixels = ThresholdUnits == UnitType.Pixels ? Threshold : Threshold * recyclerView.Height;

                //If the layout direction is vertical scroll Y coord and check Y offset
                //If the layout direction is horizontal scroll X coord and check X offset
                if (layoutDirection == LinearLayoutManager.Vertical) {
                    offSet = pointInRecyclerView.Y;
                    upperThreshold = recyclerView.Height - thresholdPixels;
                    scrollY = ScrollInterval;
                } else if (layoutDirection == LinearLayoutManager.Horizontal) {
                    offSet = pointInRecyclerView.X;
                    upperThreshold = recyclerView.Width - thresholdPixels;
                    scrollX = ScrollInterval;
                }

                //if the drag position is less than the lower threshold then scroll backwards
                //if it's greater than the uper threshold then scroll forward
                //if it's in neither, the set the scroll direction to none
                var newScrollDirection = offSet < thresholdPixels
                    ? ScrollDirection.Back 
                    : offSet > upperThreshold 
                    ? ScrollDirection.Foward 
                    : ScrollDirection.None;

                InScrollPostion(newScrollDirection, recyclerView, scrollX, scrollY);

            }
        }


        Java.Util.Timer timer;
        /// <summary>
        /// Scroll continiously in the set direction while the position is in the bounds
        /// </summary>
        /// <param name="newDirection"></param>
        /// <param name="recyclerView"></param>
        /// <param name="scrollX"></param>
        /// <param name="scrollY"></param>
        /// <returns></returns>
        private void InScrollPostion(
                                ScrollDirection newDirection, 
                                RecyclerView recyclerView, 
                                int scrollX, 
                                int scrollY) {
            //if it's not already scrolling 
            if (currentScrollDirection != newDirection && newDirection != ScrollDirection.None) {
                currentScrollDirection = newDirection;
                //1 for forward -1 for backwards
                var scrollDirectionInt = newDirection == ScrollDirection.Foward ? 1 : -1;
                var canScrollDown = recyclerView.CanScrollVertically(scrollDirectionInt);
                var canScrollRight = recyclerView.CanScrollHorizontally(scrollDirectionInt);
                scrollX *= scrollDirectionInt;
                scrollY *= scrollDirectionInt;

                timer?.Cancel(); //cancel the previous timer so they don't stack
                timer?.Dispose();
                timer = new Java.Util.Timer(); //make a new timer
                //Action to be performed every timer tick
                Action action =  () => {
                    if (currentScrollDirection == newDirection
                            && (recyclerView.CanScrollVertically(scrollDirectionInt)
                            || recyclerView.CanScrollHorizontally(scrollDirectionInt))) {
                        handler.Post(() => recyclerView.ScrollBy(scrollX, scrollY));
                    } else {
                        currentScrollDirection = ScrollDirection.None;
                        timer?.Cancel();
                        timer?.Dispose();
                        timer = null;
                    }
                };
                //start the timer to do the action every 25ms
                timer.ScheduleAtFixedRate(new TimerTask(action), 0, 25);



                //the C# async way of doing it 
                //while (currentScrollDirection == newDirection
                //        && (recyclerView.CanScrollVertically(scrollDirectionInt) 
                //        || recyclerView.CanScrollHorizontally(scrollDirectionInt))) {
                //    //Not sure how this would be done in a native android way
                //    recyclerView.ScrollBy(scrollX, scrollY);
                //    await Task.Delay(25); //Go back to the ui thread for 25ms before scrolling another increment
                //}
                //currentScrollDirection = ScrollDirection.None;

            } else if (newDirection == ScrollDirection.None) {
                currentScrollDirection = ScrollDirection.None;
                timer?.Cancel();
                timer?.Dispose();
                timer = null;
            }
        }


        private class TimerTask : Java.Util.TimerTask {
            Action action;
            public TimerTask(Action action) {
                this.action = action;
            }
            public override void Run() {
                action?.Invoke(); ;
            }
        }

        /// <summary>
        /// Run this on DragAction.Ended 
        /// </summary>
        private void DragEnded() {
            currentScrollDirection = ScrollDirection.None;
            dragEvent = null;
        }

        /// <summary>
        /// Run on DragAction.Started
        /// </summary>
        /// <param name="dragEvent"></param>
        private void DragStarted(DragEvent dragEvent) {
            this.dragEvent = dragEvent;
        }


        /// <summary>
        /// This allows a newly bound cell that was previously off screen when
        /// the drag started to trigger drag events.
        /// Add this to OnBindViewHolder in override of RecyclerView.Adapter
        /// </summary>
        /// <param name="view"></param>
        public void PrepareCellView(View view) {
            handler.Post(() => {
                if (dragEvent != null && view.Parent is ViewGroup parent) {
                    parent.DispatchDragEvent(dragEvent);
                }
            });
        }


        /// <summary>
        /// This allows a newly bound cell that was previously off screen when
        /// the drag started to trigger drag events.
        /// Add this to OnBindViewHolder in override of RecyclerView.Adapter
        /// </summary>
        /// <param name="view"></param>
        public void PrepareCellView(RecyclerView.ViewHolder holder) {
            handler.Post(() => {
                if (dragEvent != null && holder.ItemView.Parent is ViewGroup parent) {
                    parent.DispatchDragEvent(dragEvent);
                }
            });
        }


        /// <summary>
        /// Find a recycler view in the views view tree
        /// </summary>
        /// <param name="view"></param>
        /// <returns></returns>
        private static RecyclerView FindRecyclerViewInTree(View view) {
            if (view == null) {
                return null;
            } else if(view is RecyclerView recyclerView) {
                return recyclerView;
            } else  {
                return FindRecyclerViewInTree(view.Parent as View);
            }

        }

        private static bool IsViewChildOfView(View child, View parent) {
            if (child == null || parent == null) {
                return false;
            } else if(child.Parent is View childsParent && childsParent == parent) {
                return true;
            } else {
                return IsViewChildOfView(child.Parent as View, parent);
            }
        }

        /// <summary>
        /// Get's a point in a view relative to another view
        /// </summary>
        /// <param name="fromView"></param>
        /// <param name="toView"></param>
        /// <param name="fromPoint"></param>
        /// <returns></returns>
        public static Point GetPointRelativeTo(View fromView, View toView, Point fromPoint) {
            var toViewRect = GetRectOnScreen(toView);
            var fromViewRect = GetRectOnScreen(fromView);
            var pointInToView = new Point(fromViewRect.Left - toViewRect.Left + fromPoint.X, 
                fromViewRect.Top - toViewRect.Top + fromPoint.Y);
            return pointInToView;
        }

        /// <summary>
        /// Gets the views Rect relative to the screen
        /// </summary>
        /// <param name="view"></param>
        /// <returns></returns>
        public static Rect GetRectOnScreen(View view) {
            int[] l = new int[2];
            view.GetLocationOnScreen(l);
            int x = l[0];
            int y = l[1];
            Rect rect = new Rect(x, y, view.Width + x, view.Height + y);
            return rect;
        }

    }
}

将RecyclerViewDragScrollHelper的实例添加到类中,并添加RecyclerDragScruller。HandleDrag()用于覆盖onDrag()。

@Override
public boolean onDrag(View view, DragEvent dragEvent) {
    recyclerDragScroller.HandleDrag(view, dragEvent);
    return true;
}

如果查看者在拖动开始时没有绑定,它不会与拖动交互,因此在适配器中将recyclerDragScroller.PrepareCellView()添加到OnBindViewHolder()重写以传递拖动事件。

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    recyclerDragScroller.PrepareCellView(viewHolder);
}
施喜
2023-03-14

我通过在 Second RecyclerView 的项目的拖动侦听器的 ACTION_DRAG_LOCATION 事件中返回 false 来解决此问题。我禁用了ACTION_DRAG_LOCATION事件,因此该事件不会被 Second RecyclerView 的 Item 的 DragListener 跟踪。那一次它的父级(Second RecyclerView)的Draglistener工作。下面的代码,我放了第二个回收器View的DragListener

case DragEvent.ACTION_DRAG_LOCATION:
RecyclerView recyclerView = (RecyclerView) viewSource.getParent();
MyAdapter adapter = (MyAdapter) recyclerView.getAdapter();
int y = Math.round(dragEvent.getY());

int translatedY = y - adapter.getScrollDistance();

int threshold = 50;
// make a scrolling up due the y has passed the threshold
if (translatedY < threshold) {
    // make a scroll up by 30 px
     mScrollView.smoothScrollBy(0, -30);

} else
    // make a autoscrolling down due y has passed the 500 px border
    if (translatedY + threshold > 500) {
        // make a scroll down by 30 px
     mScrollView.smoothScrollBy(0, 30);
    }
break;

要禁用 Second RecyclerView 的项目的 DragListener 的 ACTION_DRAG_LOCATION 事件,请使用以下代码

 @Override
    public boolean onDrag(View view, DragEvent dragEvent) {
        int action = dragEvent.getAction();
        View viewSource = (View) dragEvent.getLocalState();
        switch (action) {
            case DragEvent.ACTION_DRAG_STARTED:
                break;
            case DragEvent.ACTION_DRAG_ENTERED:

                break;
            case DragEvent.ACTION_DRAG_LOCATION:

                return false;
            case DragEvent.ACTION_DRAG_EXITED:

                break;
            case DragEvent.ACTION_DROP:

                break;
            default:
                break;
        }

        return true;
    }

因此,无论您需要处理来自Draglistener的任何事件,您只需要返回true,否则返回false。

 类似资料:
  • 实现循环ScrollView。有以下特色: 1、循环的scrollview 2、类似于tableview的编程方式 3、可定制化的内容 4、灵活运用可用于移步加载图片 5、结构化,可扩展性高 [Code4App.com]

  • 我有一个水平ScrollView,它有两个元素:CardView和水平RecycerView。所以,当用户水平滚动时,我希望这两个元素滚动。 我想有这样的东西:Goal,橙色的线是CardView,黄色的线是RecycerView。当用户滚动时,两个元素滚动,如下所示:Scrolled Goal。 现在在我的应用程序中,当用户滚动时,只有RecycerView滚动。CardView保持在他的位置。

  • 我是一个相当有经验的< code>Eclipse用户,并且刚刚转向< code>IntelliJ Idea(或者至少,正在尝试)。 在中,我习惯了项目视图的“与编辑器链接”功能。我刚刚发现中类似的功能称为“从源代码自动滚动”(有点模糊,但没问题)。它突出显示了“项目”视图中当前打开的文件。 我们的项目由大约30个Maven模块组成,所以我想知道“Maven项目”视图是否有相同的选项。我检查了Mav

  • 我目前正在使用JavaFX ScrollBar控件,它本身运行良好-但是我对拖动拇指时的“动画”不满意。 更具体地说: 当我快速拖动拇指时,快速加速-拇指动画不会立即跟随,但有点“滞后”-当我停止拖动拇指时的相同行为。。。拇指真正开始/追上预定位置需要一秒钟的时间 这不是繁重的布局计算之类的问题,因为我只是单独渲染ScrollBar而没有任何内容。 是否有一些选项可以让这个“加速动画”/“滞后”消

  • 我尝试在JTable中捕获鼠标滚轮事件。不拖动时,它接收MouseWheelMove事件,但拖动时,它什么也不接收。我的猜测是鼠标滚轮事件被传递给与TransferHandler相关的东西,而不是JTable。 如何在拖动时检测鼠标滚轮运动?只要达到我的目的,任何其他方法都是受欢迎的。

  • 我遇到了自动布局的问题,似乎无法找到应该很容易实现的答案。 我有以下视图层次结构: 标签上的前导/尾随限制使它们在更薄的设备上更高(iPhone 4s vs iPhone 6)。 为了让UIScrollview正常工作,我需要在UIScrollView内部设置UIView的高度约束,以避免出现“不明确的高度”警告。 但在iPhone 4s上运行时,UIView不够高,无法容纳它的子视图。 到目前为