• 安德鲁斯 建立与各种听众自己定义的ScrollView


    === 建立与各种听众自己定义的ScrollView ===

     

    尽管安卓5.1已经release, 可是ScrollView的封装和对外API依然少的可怜, 尽管它优化得非常好了.

    所以问题来了: ScrollView滑动方向是什么, 何时停止? 所以本文的目标出现了: 解决这些看似小, 可是用起来却非常燃眉的问题!

     

    首先思考: 怎样知道ScrollView是否在滚动, 只是这一点请放心, SDK还是提供了这个功能, 不然SDK也太烂了. 呵呵, 找打了, 首先请继承ScrollView这个父类, 毕竟非常多东西拿来主义是没问题的.

    publicclass MyScrollViewextends ScrollView {

     

           privatefinal StringTAG = this.getClass().getSimpleName();

     

           publicMyScrollView(Contextcontext) {

                  super(context);

           }

     

           publicMyScrollView(Contextcontext, AttributeSetattrs) {

                  super(context,attrs);

           }

     

           publicMyScrollView(Contextcontext, AttributeSet attrs, int defStyle) {

                  super(context,attrs,defStyle);

           }

    }

     

    这个我就不多解释了, 自己定义过控件的人肯定知道这三个构造的意义, 多一嘴, 当中的第二个是给XML 来 Render的, 所以一定要写上.

     

    来, 写上刚才说的最关键的:

    @Override

    protectedvoidonScrollChanged(int l,int t,int oldl,int oldt) {

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

    }

     

    简单说明一下, API默认的这四个參数的意思非常隐晦, 你能够考虑改写成:

    @Override

    protectedvoidonScrollChanged(int currentX,int currentY,int oldx,int oldy) {

    super.onScrollChanged(currentX,currentY,oldx,oldy);

    }

     

    既然须要进行监听状态改变, 那么使用回调的设计进行监听再好只是(之所以选择抽象类而不是使用接口, 是由于这种话能够更好地让用户进行抉择, 详细须要重写哪一个, 简化代码量和降低流程使用的难度), 这里推荐使用内部类, 当然, 新建一个独立新类也是能够的:

    publicabstractclass MyScrollViewListener {

           publicvoidonMyScrollChanged(MyScrollView scrollView,int x,int y,int oldx,int oldy) {

           }

     

           publicvoidonMyScrollStart(MyScrollView scrollView,int x,int y,int oldx,int oldy) {

           }

     

           publicvoidonMyScrollStop(MyScrollView scrollView,int x,int y,int oldx,int oldy) {

           }

     

           publicvoidonMyScrollTop(MyScrollView scrollView,int x,int y,int oldx,int oldy) {

           }

     

           publicvoidonMyScrollBottom(MyScrollView scrollView,int x,int y,int oldx,int oldy) {

           }

     

           publicvoidonMyScrollUp(MyScrollView scrollView,intx,inty,intoldx,intoldy) {

           }

     

           publicvoidonMyScrollDown(MyScrollView scrollView,int x,int y,int oldx,int oldy) {

           }

    }

     

    简单解释一下, 自上而下的回调监听的意思各自是:

    onMyScrollChanged: 当滚动时

    onMyScrollStart: 当開始滚动时

    onMyScrollStop: 当滚动停止时

    onMyScrollTop: 当滚动到到顶部时

    onMyScrollBottom: 当滚动究竟部时

    onMyScrollUp: 当向上滚动时

    onMyScrollDown: 当向下滚动时

     

    这些都是比較经常使用的, 先写好, 我们一个一个来实现. 注意 内部类的话, 回调方法要使用 public的. 不然外部回调的发起者, 是无法重写回调方法的.

     

    哦, 对了, 进一步集成, 方便外部发起者进行调用:

    private MyScrollViewListener myScrollViewListener;

     

    public void setMyScrollViewListener(MyScrollViewListener myScrollViewListener) {

           this.myScrollViewListener =myScrollViewListener;

    }

     

    非常easy, 一个set方法 来相应刚才的回调抽象类, 不解释了.

     

    前方高能, 核心功能!

    @Override

    protected void onScrollChanged(int l,int t,int oldl,int oldt) {

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

     

           if (myScrollViewListener !=null) {

                  myScrollViewListener.onMyScrollChanged(this,l,t, oldl, oldt);

           }

    }

     

    好理解吧?? 这个就是我之前说的SDK自己提供的监听. 既然继承了 ScrollView, 直接重写, 先显式调用父类的方法, 然后正好, 回调一下我们的onMyScrollChanged监听, 满足题意, 当然必须有发起者才干够, 所以加了一个非空推断. 继续!

     

    @Override

    protectedvoidonScrollChanged(intl,intt,intoldl,intoldt) {

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

     

           if (myScrollViewListener !=null) {

                  myScrollViewListener.onMyScrollChanged(this,l,t, oldl, oldt);

     

                  if (t -oldt > 0) {

                         myScrollViewListener.onMyScrollDown(this,l,t, oldl, oldt);

                         Log.i(TAG,"正在向下滚动");

                  } else {

                         myScrollViewListener.onMyScrollUp(this,l,t, oldl, oldt);

                         Log.i(TAG,"正在向上滚动");

                  }

           }

    }

     

    这个也好理解吧, t 之前说过, 是 纵向的偏移量, 你能够用Log看看变化规律, 看代码事实上也能明确. 然后依据条件进行回调, OK, 完毕!

     

    @Override

    protectedvoidonScrollChanged(int l,int t,int oldl,int oldt) {

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

     

           if (myScrollViewListener !=null) {

                  myScrollViewListener.onMyScrollChanged(this,l,t, oldl, oldt);

     

                  if (getScrollY() <= 0) {

                         myScrollViewListener.onMyScrollTop(this,l,t, oldl, oldt);

                         Log.i(TAG,"到达了顶部");

                  }

     

                  //====================================================

     

                  View view = (View)getChildAt(getChildCount() - 1);//获取 ScrollView最后一个控件

                  int diff = (view.getBottom() - (getHeight() +getScrollY()));

     

                  if (diff == 0) {

                         myScrollViewListener.onMyScrollBottom(this,l,t, oldl, oldt);

                         Log.i(TAG,"到达了底部");

                  }

           }

    }

     

    简单说明一下:

    getScrollY能够获取ScrollView顶部位置的像素值, 详细的看API, 不赘述.


    还是看图吧, 不多说了, 自己用绘图做的, 比較粗糙,可是顾名思义.




    三个各自是滚动到了顶端, 滚动在中间, 滚动到了底部.

     

    至此, 简单的功能都完毕了, 剩下一个最难的了, 立刻攻克之!

    思考: 想知道何时停止的话, 能够使用类似于Observer的方式进行监听, 我第一想法是用for, 后来细致想想, Thread.sleep等方式尽量避免, UI都会卡掉. 所以当滚动開始的时候, 通过postDelayed()自身延迟自己调用自己重复复运行(安卓中比較常见的设计).

     

    来, 看代码:

    @Override

    protected void onScrollChanged(int l,int t,int oldl,int oldt) {

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

     

           if (myScrollViewListener !=null) {

                  myScrollViewListener.onMyScrollChanged(this,l,t, oldl, oldt);

     

                  if (!scrollerTaskRunning) {

                         startScrollerTask(this,l,t, oldl, oldt);

                  }

           }

    }

     

    private Runnable scrollerTask;

    private int initialPosition;

    private int newCheck = 50;

    private boolean scrollerTaskRunning =false;

     

    private void startScrollerTask(final MyPullableScrollView scrollView,final int x,final int y,final int oldx,final int oldy) {

           if (!scrollerTaskRunning) {

                  myScrollViewListener.onMyScrollStart(this,x,y, oldx, oldy);

                  Log.i(TAG,"滚动開始");

           }

     

           scrollerTaskRunning = true;

     

           if (scrollerTask ==null) {

                  scrollerTask = new Runnable() {

                         public void run() {

                                int newPosition =getScrollY();

     

                                if (initialPosition -newPosition == 0) {

                                      if (myScrollViewListener !=null) {

                                             scrollerTaskRunning =false;

     

                                             myScrollViewListener.onMyScrollStop(scrollView,x,y, oldx, oldy);

                                             Log.i(TAG,"滚动结束");

                                       }

                                } else {

                                      startScrollerTask(scrollView,x,y, oldx, oldy);

                                }

                         }

                  };

           }

     

           initialPosition = getScrollY();

           postDelayed(scrollerTask, newCheck);

    }

     

    最后解释一下, scrollerTask 是一个启动线程的任务, initialPosition 是滚动的初始位置, newCheck 表示postDelayed的推迟频率, 用来实现自身调用的循环.scrollerTaskRunning表示滚动是否開始, 防止重复运行.

     

    这里(还有向上向下滚动)我之前走了弯路, 使用的是网上提供的 重写 view.onTouch() 或者 view.onTouchEvent(), 在 MotionEvent.ACTION_UP 或者 ACTION_MOVE 的时候调用scrollerTask来实现, 可是这样 參数(什么oldX 之类的) 不是非常好传递, 尽管 Event 也能够简单控制, 可是我不想用太多松散的关系类, 所以直接加入 scrollerTaskRunning 这个变量来详细控制 方法内的推断时机.

     

    推断的原理是, 不停地比較当前的currentScrollY和 上一次的 lastScrollY, 假设一样, 则 停止延迟地自身调用自身, 否则,继续延迟地自身调用自身, 再来一次比較, 以此类推. scrollerTaskRunning变量的控制要注意, 这样便能够有效地防止多次无意义的运行.

     

    网上提供的方案非常多是在 构造函数中 初始化 scrollerTask, 可是这样回调拿不到參数, 并且必须用上面说的重写, 还须要额外的成员属性, 重复计算再使用, 麻烦繁琐. 所以我改造了代码, 就能够避免这个问题(怎么感觉语序有一些乱套… 语文渣).

     

    好吧, 贴一下全部的代码, log 都改成了 英文, 既尊重资料提供者, 又能够避免乱码, 加入package就能够直接用了, 自己定义控件怎么用, 我就不说了, 网上有非常多, 祝你好运.

    import android.content.Context;

    import android.util.AttributeSet;

    import android.util.Log;

    import android.view.View;

    import android.widget.ScrollView;

     

    public class MyScrollView extends ScrollView {

     

           private final StringTAG = this.getClass().getSimpleName();

     

           public MyScrollView(Context context) {

                  super(context);

           }

     

           public MyScrollView(Context context, AttributeSet attrs) {

                  super(context,attrs);

           }

     

           publicMyScrollView(Contextcontext, AttributeSetattrs, intdefStyle) {

                  super(context,attrs,defStyle);

           }

     

           public abstract class MyScrollViewListener {

                  public void onMyScrollChanged(MyScrollView scrollView,int x,int y,int oldx,int oldy) {

                  }

     

                  public void onMyScrollStart(MyScrollView scrollView,int x,inty,intoldx,intoldy) {

                  }

     

                  public void onMyScrollStop(MyScrollView scrollView,int x,int y,int oldx,int oldy) {

                  }

     

                  public void onMyScrollTop(MyScrollView scrollView,int x,int y,int oldx,int oldy) {

                  }

     

                  public void onMyScrollBottom(MyScrollView scrollView,int x,int y,int oldx,int oldy) {

                  }

     

                  public void onMyScrollUp(MyScrollView scrollView,int x,int y,int oldx,int oldy) {

                  }

     

                  public void onMyScrollDown(MyScrollView scrollView,int x,int y,int oldx,int oldy) {

                  }

           }

     

           private MyScrollViewListener myScrollViewListener;

     

           public void setMyScrollViewListener(MyScrollViewListener myScrollViewListener) {

                  this.myScrollViewListener =myScrollViewListener;

           }

     

           @Override

           protected void onScrollChanged(intl,intt,intoldl,intoldt) {

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

     

                  if (myScrollViewListener !=null) {

                         myScrollViewListener.onMyScrollChanged(this,l,t, oldl, oldt);

     

                         //====================================================

     

                         if (!scrollerTaskRunning) {

                                startScrollerTask(this,l,t, oldl, oldt);

                         }

     

                         // ====================================================

     

                         if (t -oldt > 0) {

                                myScrollViewListener.onMyScrollDown(this,l,t, oldl, oldt);

                                Log.i(TAG,"is scrolling down");

                         } else {

                                myScrollViewListener.onMyScrollUp(this,l,t, oldl, oldt);

                                Log.i(TAG,"is scrolling up");

                         }

     

                         //====================================================

     

                         if (getScrollY() <= 0) {

                                myScrollViewListener.onMyScrollTop(this,l,t, oldl, oldt);

                                Log.i(TAG,"the top has beenreached");

                         }

     

                         // ====================================================

     

                         Viewview = (View)getChildAt(getChildCount() - 1);// We take the last son in thescrollview

                         intdiff = (view.getBottom() - (getHeight() +getScrollY()));

     

                         if (diff == 0) {

                                myScrollViewListener.onMyScrollBottom(this,l,t, oldl, oldt);

                                Log.i(TAG,"the bottom has beenreached");

                         }

                  }

           }

     

           private Runnable scrollerTask;

           private int initialPosition;

           private int newCheck = 50;

           private boolean scrollerTaskRunning =false;

     

           private void startScrollerTask(final MyScrollView scrollView,final int x,final int y,final int oldx,final int oldy) {

                  if (!scrollerTaskRunning) {

                         myScrollViewListener.onMyScrollStart(this,x,y, oldx, oldy);

                         Log.i(TAG,"scroll start");

                  }

     

                  scrollerTaskRunning = true;

     

                  if (scrollerTask ==null) {

                         scrollerTask = new Runnable() {

                                public voidrun() {

                                      int newPosition = getScrollY();

     

                                      if (initialPosition -newPosition == 0) {

                                             if (myScrollViewListener !=null) {

                                                    scrollerTaskRunning =false;

     

                                                    myScrollViewListener.onMyScrollStop(scrollView,x,y, oldx, oldy);

                                                    Log.i(TAG,"scroll stop");

                                                    return;

                                              }

                                       }else {

                                             startScrollerTask(scrollView,x,y, oldx, oldy);

                                       }

                                }

                         };

                  }

     

                  initialPosition = getScrollY();

                  postDelayed(scrollerTask, newCheck);

           }

    }

     

    ======================================================

    感谢微软必应, stackoverflow 大神和 Google大神提供的思路. 没有他们, 我, 寸步难行.

    ======================================================

    停止滚动的代码, 參考了这个问题:

    http://stackoverflow.com/questions/8181828/android-detect-when-scrollview-stops-scrolling

     

     

     

    Presented by imknown

    2015-03-19

    版权声明:本文博主原创文章,博客,未经同意不得转载。

  • 相关阅读:
    HDU3461 Code Lock 并查集应用
    记录,待总结8
    HDU1325 Is It A Tree?
    函数指针总结
    记录,待总结6
    HDU1272 小希的迷宫 并查集
    记录,待总结10
    记录,待总结9
    C# 获取radiobutton的值
    解决idea控制台tomcat输出中文乱码
  • 原文地址:https://www.cnblogs.com/zfyouxi/p/4777567.html
Copyright © 2020-2023  润新知