ScrollView 相关操作(滚动,判断位置)

后面持续as

问题

(1)解决ScrollView加载完数据后会滑到下面   https://blog.csdn.net/ctluo111/article/details/72901526
用ScrollView嵌套Webview或者listview之,当加载数据完之后,scrollview会滑动一段距离这个问题。最开始的时候用view.postdelay,让数据加载完之后scrollview.scrollto(0,0);
这样去处理,但是也出现问题,delay延迟的时间比较短的话,好像没效果还是会滑动一段距离,delay时间长的话会出现界面下面闪下在变到最上面,体验不好。
经研究发现是因为焦点问题,scrollview会滑动到焦点所在的子view中,解决方法也简单,在ScrollView嵌套的第一个Linearlayout中加入如下2行代码就好:
android:focusable="true"
android:focusableInTouchMode="true"

一、 Android 控制ScrollView滚动到底部,顶部,指定位置

1.1

public static void scrollToBottom(final View scroll, final View inner) {  
  
Handler mHandler = new Handler();  
  
mHandler.post(new Runnable() {  
public void run() {  
if (scroll == null || inner == null) {  
return;  
}  
int offset = inner.getMeasuredHeight() - scroll.getHeight();  
if (offset < 0) {  
offset = 0;  
}  
  
scroll.scrollTo(0, offset);  
}  
});  
}  

使用fullScrol()

下面我们看一下这个函数:

scrollView.fullScroll(ScrollView.FOCUS_DOWN);滚动到底部
scrollView.fullScroll(ScrollView.FOCUS_UP);滚动到顶部

需要注意的是,该方法不能直接被调用
因为Android很多函数都是基于消息队列来同步,所以需要一部操作,
addView完之后,不等于马上就会显示,而是在队列中等待处理,虽然很快,但是如果立即调用fullScroll, view可能还没有显示出来,所以会失败
应该通过handler在新线程中更新

handler.post(new Runnable() {  
    @Override  
    public void run() {  
        scrollView.fullScroll(ScrollView.FOCUS_DOWN);  
    }  
});  



1.2 滑动到指定位置


  handler.post(new Runnable() {  
                @Override  
                public void run() {  
                    //To change body of implemented methods use File | Settings | File Templates.  
//                    mRootScrollView.fullScroll(ScrollView.FOCUS_DOWN);  
                    int[] location = new int[2];  
                    view.getLocationOnScreen(location);  --- 获取位置
                    int offset = location[1] - mRootScrollView.getMeasuredHeight();  
                    if (offset < 0) {  
                        offset = 0;  
                    }  
                    mRootScrollView.smoothScrollTo(0, offset);  
                }  
            });  



第一,handler.post(runnable);并不是新开线程,只是让UI主线程去并发执行run()方法。
第二,之所以放在handler里,是为了保证View都已经绘制完成。不然,你放在resume()中执行,应该也可以的。
第三,smoothScrollTo类似于scrollTo,但是滚动的时候是平缓的而不是立即滚动到某处。另外,smoothScrollTo()方法可以打断滑动动画。



1.3
ScrollView滚动到指定位置 (平滑 慢速 动画)-- 添加动画

private Runnable runnable = new Runnable() {

        @Override
        public void run() {

            scrollToPosition(0,600);
        }
    };



    public void scrollToPosition(int x,int y) {

        ObjectAnimator xTranslate = ObjectAnimator.ofInt(ScrollContainer, "scrollX", x);
        ObjectAnimator yTranslate = ObjectAnimator.ofInt(ScrollContainer, "scrollY", y);

        AnimatorSet animators = new AnimatorSet();
        animators.setDuration(1000L);
        animators.playTogether(xTranslate, yTranslate);
        animators.addListener(new Animator.AnimatorListener() {

            @Override
            public void onAnimationStart(Animator arg0) {
                // TODO Auto-generated method stub
            }

            @Override
            public void onAnimationRepeat(Animator arg0) {
                // TODO Auto-generated method stub

            }

            @Override
            public void onAnimationEnd(Animator arg0) {
                // TODO Auto-generated method stub

            }

            @Override
            public void onAnimationCancel(Animator arg0) {
                // TODO Auto-generated method stub

            }
        });
        animators.start();
    }



二、 ScrollView判断是否滑动到底部和顶部  https://blog.csdn.net/tianzhu2725/article/details/52758693 

1. getMeasuredHeight()是实际View的大小,与屏幕无关,
   getMeasureHeight()是View的实际高度,也就是说不管view是否可见,是否部分可见,我们得到的始终是一个固定值。

2. getHeight的大小此时则是屏幕的大小
   而getHeight()是按照用户的视角来说的,也就是“用户看到多少,getHeight()返回值就是多少”,当View超出屏幕范围的时候,
   此时得到的就是View的实际高度-超出屏幕范围的高度

3. scrollview 子 view 的高度

   View childView = getChildAt(0 //i);
   childView.getMeasuredHeight()  表示得到子View的实际高度,


4.关于滚动:scrollTo、scrollBy、getScrollX、getScrollY这几个方法的区别:

ScrollTo()表示移动视图到那个坐标点,就是说那个视图调用这个方法, 
那么这个视图的(x,y)点就和此视图原始位置的(0,0)点对其,二View的坐标系统是左上角为(0,0),向右为正,向下为正,因此ScrollTo(0,30)
就是要把此视图的(0,30)点移动到原始位置(0,0)点,具体向上还是向下完全看当前的视图的位置来决定吗(关键认知:所有的滑动都是移动视图的)

ScrollBy()表示在视图的X、Y方向上各移动dx,dy距离,同样向上为正, 
向左为正,例如:ScrollBy(0,30)表示将此视图向上移动30dpi,为什么是这样呢?因为view的位置是相对于其父容器来说的,假设刚开始View的左上角和父容器重合View高度和宽度为50dpi,
那么View的位置是(0,0,50,50),然后向上移动30dpi,此时View的位置是(0,-30,50,20),那么原视图的位置减去移动后的位置,结果就是正的30dpi(源码中ScrollBy其实是对ScrollTo的再封装)

在源码中有两个变量:MScrollX,MScrollY,这两个变量是专门用于记录位 
置的变量,也就是当前View的位置,ScrollTo()源码如下: “`
public void scrollTo(int x, int y) {  
    if (mScrollX != x || mScrollY != y) {  
       int oldX = mScrollX;  //将当前位置设置为老的位置
        int oldY = mScrollY;  
        mScrollX = x;  //将需要滚动到的位置设置为当前位置
        mScrollY = y;  
        invalidateParentCaches();  
        onScrollChanged(mScrollX, mScrollY, oldX, oldY);  
       if (!awakenScrollBars()) {  
           postInvalidateOnAnimation();  
       }  
    }  
}  

//ScrollBy也是和MScrollX和MScrollY相关的
public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
}

而getScrollX()和getScrollY()方法源码中就是直接返回MScrollX和 
MScrollY的值,在观察上面的源码,你会发现MScrollX的值是等于ScrollTo(中设置的值的),因此:getScrollY = 
mScrollY = ScrollTo(0,Y)
总结:假如现在view在初始位置,然后我们向上滑动50dpi,就是让view的(0,50)和原视图的(0,0)重合,此时相当于调用ScrollTo(0,50),
此时的MScrollY等于50,因此getScrollY()的返回值就是50


5.  ScrollView只能放一个子View,当ScrollView滑动到最底部时,此时ScrollView内容的高度就是:超出屏幕的高度+此控件的高度,

childView.getMeasureHeight() == getScrollY + chilView.getHeight()

而当不是ScrollView是lIstView这样可以有多个子View的视图的时候,我们可以这样做:

View view = (View)getChildAt(getChildCount()-1);
int d = view.getBottom();
d = (getHeight()+getScrollY());
if(d == 0){
//滑动到底部  
}


mItemCount = getAdapter().getCount();
所以getCount()和getView()是一对,存在于适配器Adapter中


总之你要记住:如果你想对可见区域的某一个位置的item操作就使用getChildAt()和getChildCount(),对总的操作就使用adapter里面的getCount()和getView()


6. 

6.1实现OnTouchListener来监听是否滑动到最底部
OnTouchListener onTouchListener=new OnTouchListener(){    
            @Override    
            public boolean onTouch(View v, MotionEvent event) {   
                switch (event.getAction()) {  
                    case MotionEvent.ACTION_UP:  
		        //底部
                        if (childView  != null && childView .getMeasuredHeight() <= getScrollY() + getHeight()) {  
                                               //getScrollY() + getHeight() - getPaddingTop()-getPaddingBottom() == getChildAt(0).getMeasuredHeight()  有padding的情况
			}
			
			else if (getScrollY() == 0) {  //顶部
                        
			} 
			else{
			//中间
			}
                    break;  
                }  
                return false;  
            }  
 }

 scroll_view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // 判断 scrollView 当前滚动位置在顶部
                if(scroll_View.getSrollY == 0){
                }

                //底部
                if (scroll_view.getChildAt(0).getHeight() - scroll_view.getHeight()  == scroll_view.getScrollY()){
                }
                return false;
            }
        });



6.2  onScrollChanged 监听 --- 更好

public class myScrollView extends ScrollView {  
    public myScrollView(Context context)  
    {  
        super(context);  
    }  
    public myScrollView(Context context, AttributeSet attributeSet)  
    {  
        super(context,attributeSet);  
    }  
  
    @Override  
    protected void onScrollChanged(int l, int t, int oldl, int oldt)  
    {  
        //1   
        View view = (View)getChildAt(getChildCount()-1);  
        int d = view.getBottom();  
        d -= (getHeight()+getScrollY());  
        if(d==0)  {  
	  //
        } 


        else  
            super.onScrollChanged(l,t,oldl,oldt);  
    }  



四、 https://blog.csdn.net/qq_33387077/article/details/51838837


//拖动监听,使 scrollview 进行动态变换  
使用EditText:在ScrollView包裹的最底部View中加入一个EditText,EditText 获取焦点,自动滚动到最底部

View.OnDragListener roomOnDragListener = new View.OnDragListener() {

                @Override
                public boolean onDrag(View v, DragEvent event) {
                    final int action = event.getAction();
                    switch (action) {
                        case DragEvent.ACTION_DRAG_STARTED:
                            ////todo
                            if (gridView.getChildCount() % 3 == 0 && gridView.getChildCount() != 0) {
                                mEtBotton.setVisibility(View.VISIBLE);
                                mEtBotton.requestFocus();
                            }
                            return true;
                        case DragEvent.ACTION_DRAG_ENDED:
                            //todo
                            mEtBotton.setVisibility(View.GONE);
                            break;
                        case DragEvent.ACTION_DROP:
                            // 初始控件
                            View view = (View) event.getLocalState();
                            if (view == null) {
                                return false;
                            }

                            // 放落控件
                            ViewGroup owner = (ViewGroup) view.getParent();
                            if (owner != null && owner.getId() == R.id.room_type_rv) {    // 房间类别拖动
                                RoomTypeContent roomType = mRoomTypeAdapter.getItem((int) view.getTag());
                                addRoom(roomType, ctrlNodeId);    // 外面添加了数据库更新监听,这儿不用去更新界面了

                                //todo
                                mEmptyTV.setVisibility(View.GONE);
                            } else if (view.getTag() instanceof Room) {    // 房间内拖动
                                ...
                                ...

                            //todo
                            mEtBotton.setVisibility(View.GONE);
                            return true;
                        case DragEvent.ACTION_DRAG_LOCATION:
                            return true;
                    }
                    return false;
                }
            };

            // 如果只有一个节点,左边的所有区域都是
            if (ctrlNodes.size() == 1) {
                container.setOnDragListener(roomOnDragListener);
            } else {
                layout.setOnDragListener(roomOnDragListener);
            }



五、系统原生提供的方法  和自己判断的区别 https://blog.csdn.net/wuyou1336/article/details/54645132

1. onScrollChanged方式,自己计算

2. onOverScrolled使用系统计算的结果,api >= 9才支持

可能忽视的细节1:

如果是手势滑动,上面两种方式都对,但是如果是调用ScrollView的smoothScrollTo和scrollTo方法来滚动的话,

只有onScrollChanged监听对,onOverScrolled监听不对,因为通过代码来滚动话是精确滚动,onOverScrolled方法没处理这种情况


惯性滚动监听

public class ListeningScrollView extends ScrollView {

    /**
     * 上次的Y坐标
     */
    private int lastY = 0;
    private ScrollYListener scrollYListener;
    private ScrollListener scrollListener;
    private static final int SCROLL_TIME = 20;

    private static final int SCROLL_WHAT = 111;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case SCROLL_WHAT:
                    int scrollY = getScrollY();
                    DebugUtils.printLogE("收到惯性滑动事件" + scrollY);
                    if (lastY != scrollY) {
                        lastY = scrollY;
                        scrollYListener.onScrollChanged(scrollY);
                        handler.sendEmptyMessageDelayed(SCROLL_WHAT, SCROLL_TIME);
                    }
                    break;
            }
        }
    };

    public ListeningScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setScrollYViewListener(ScrollYListener scrollYListener) {
        this.scrollYListener = scrollYListener;
    }

    public void setScrollViewListener(ScrollListener scrollListener) {
        this.scrollListener = scrollListener;
    }


    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (scrollYListener != null) {
            scrollYListener.onScrollChanged(t);
        }
        if(scrollListener != null){
            scrollListener.onScrollChanged(l,t,oldl,oldt,computeVerticalScrollRange());
        }
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_UP:
                if (scrollYListener != null) {
                    handler.sendEmptyMessage(SCROLL_WHAT);
                    DebugUtils.printLogE("发送滑动事件");
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

    /**
     * 带惯性的滑动监听器
     */
    public interface ScrollYListener {
        void onScrollChanged(int y);
    }

    /**
     * 滑动监听器
     */
    public interface ScrollListener {
        void onScrollChanged(int x, int y, int oldx, int oldy,int computeVerticalScrollRange);
    }
}





猜你喜欢

转载自blog.csdn.net/kongbaidepao/article/details/79791401
今日推荐