后面持续as
问题
用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); } }