zoukankan      html  css  js  c++  java
  • 【Android】无限滚动的HorizontalScrollView

    这是一个很简单的功能,作为新手,做一下笔记。也给其它有需要的人提供一个参考。

      首先HorizontalScrollView都知道用途了。它可以实现类似“桌面程序”左右切换页的效果。一般情况下里面的页数都是固定的,但是也有可能遇到不固定页数的,比如动态加载照片,或者像我这次需要实现的情况。

      实现功能:实现日历的“日视图”。一页表示某一天的情况,向右翻页可翻到下一天,向左翻到上一天。而且可以随意翻到任意一天。每一页记录有当天的待办事项(这个就另外写了,这里只实现无限左右翻页)。

      由于每天作为一页,把所有天数都加载到ScrollView中是不现实的。考虑到内存占用问题,ScrollView中肯定不要放太多东西的好。所以只放3页。如下图所示:

      HorizontalScrollView中仅有3页。在第0页,向左翻的时候,松手的一瞬间,1页消失,-2页加载,然后smoothScroll到 -1页。向右亦然。因此可以保证不占用过多内存,又可以无限翻页。

      实现的时候还遇到一点小问题,顺便也一起写下来。

      一开始我很自然地想到重写HorizontalScrollView。即代码中的KamHorizontalScrollView

    1. 主要是重写onTouchEvent方法,用于处理用户手指滑动事件。
    2. 由于每个子View占一屏,不可以出现两个View各占一半的现象,需要有一个将子View分页化的方法。在我的代码中就是public boolean scrollToPage(int);该方法传入的是页码,可以保证滑动到正确的位置,而不会滑一半。
    3. 还有一点要注意,翻页的时候,为了保证ScrollView只有3页,需要增加删除子View。在末尾增删没有问题,但在首部增加,所有的子View会向后移动,在首部删除,所有的子View会向前移动,因此这两个操作需要立即改变ScrollView的scroll值以保证屏幕显示顺滑。这一点在方法public boolean addLeft(View);和public boolean removeLeft();中均有体现。

      接下来是我写代码过程中出现的一些问题:

      1、重写HorizontalScrollView后,我在构造函数中进行初始化,添加三个初始的子View,报错。

      解决:构造函数调用的时候,ScrollView还没有实例化,因此这个时候不能添加子View。应该等实例化之后再添加。重写protected void onFinishInflate();方法即可得到实例化完成的时机,在该方法下初始化。

      2、左右滑动的时候,我调用的是scrollToPage(1);但是总是滑动到第2页或第0页。就是不能定在第1页。

      解决:这个问题我花了不少时间(其实如果我去认真看看API就能很快搞定的)。通过Log,发现addView之后,新的View的Left值仍是0。或者说addView之后的一瞬间,layout中的所有子View还是保持原有的状态。过了一阵子才又重新排列的。所以我需要获得他们重新排列的时机,才能scroll到正确位置。之前写JavaSE的自定义布局有重写过排列布局的方法,所以这个也有。说白了就是onLayout方法(我以为是onMeasure,试了不行很纠结)。onLayout方法中,应该就是对所有子View进行重新排列了。(这一点可以自己去试试,先addView,然后立刻获取刚才这个View的位置和尺寸,会发现都是0。然后你可以通过按键事件再获取一次,会发现得到正确值了。因为从addView到按键这段时间足够他重新排列了。)

      所以通过LinearLayout.addOnLayoutChangeListener(listener);就可以监听重新排列的时机。但是该方法需要APILevel 11及以上。刚好我在用我的G12测试,APILevel10。虽然很纠结,我也只能重写了LinearLayout,即代码中的KamLinearLayout,还有自定义监听器kamLayoutChangeListener。重写仅仅是为了在Android2.3监听onLayout。

      另外加了一点小细节,翻页的机制除了手指滑动的距离,还有手指滑动的速度。自己写的SpeedChange三个方法。测试了一下感觉效果挺不错。

      最后把源码附上,注释写了比较详细的,希望能帮助到初学者。不要像我走太多弯路。(注意改包名)

    KamHorizontalScrollView.java

    复制代码
      1 package com.kam.horizontalscrollviewtest.view;
      2 
      3 import com.kam.horizontalscrollviewtest.R;
      4 
      5 import android.content.Context;
      6 import android.graphics.Color;
      7 import android.util.AttributeSet;
      8 import android.util.DisplayMetrics;
      9 import android.util.Log;
     10 import android.view.Gravity;
     11 import android.view.MotionEvent;
     12 import android.view.View;
     13 import android.view.ViewGroup;
     14 import android.widget.HorizontalScrollView;
     15 import android.widget.LinearLayout;
     16 import android.widget.TextView;
     17 import android.widget.FrameLayout.LayoutParams;
     18 /*如果不需要支持Android2.3,可以将代码中所有KamLinearLayout替换为ViewGroup*/
     19 public class KamHorizontalScrollView extends HorizontalScrollView {
     20     private static String tag = "KamHorizontalScrollView";
     21     private Context context;
     22     
     23     /*记录当前的页数标识(做日视图的时候可以和该值今日的日期作差)*/
     24     private int PageNo=0;
     25     
     26     /*保存ScrollView中的ViewGroup,如果不需要支持Android2.3,可以将KamLinearLayout替换为ViewGroup*/
     27     private KamLinearLayout childGroup = null;
     28     
     29     /*这是判断左右滑动用的(个人喜好,其实不需要这么麻烦)*/
     30     private int poscache[] = new int[4];
     31     private int startpos;
     32     
     33     public KamHorizontalScrollView(Context context, AttributeSet attrs,
     34             int defStyle) {
     35         super(context, attrs, defStyle);
     36         // TODO Auto-generated constructor stub
     37         this.context=context;
     38     }
     39     public KamHorizontalScrollView(Context context, AttributeSet attrs) {
     40         super(context, attrs);
     41         // TODO Auto-generated constructor stub
     42         this.context=context;
     43     }
     44     public KamHorizontalScrollView(Context context) {
     45         super(context);
     46         // TODO Auto-generated constructor stub
     47         this.context=context;
     48     }
     49     
     50     /*重写触摸事件,判断左右滑动*/
     51     @Override
     52     public boolean onTouchEvent(MotionEvent ev) {
     53         switch (ev.getAction()) {
     54         case MotionEvent.ACTION_DOWN:
     55             startpos = (int) ev.getX();
     56             /*用于判断触摸滑动的速度*/
     57             initSpeedChange((int) ev.getX());
     58             break;
     59         case MotionEvent.ACTION_MOVE: {
     60             /*更新触摸速度信息*/
     61             movingSpeedChange((int) ev.getX());
     62         }
     63             break;
     64         case MotionEvent.ACTION_UP:
     65         case MotionEvent.ACTION_CANCEL: {
     66             /*先根据速度来判断向左或向右*/
     67             int speed = releaseSpeedChange((int) ev.getX());
     68             if(speed>0){
     69                 nextPage();
     70                 return true;
     71             }
     72             if(speed<0){
     73                 prevPage();
     74                 return true;
     75             }
     76             
     77             /*这里是根据触摸起始和结束位置来判断向左或向右*/
     78             if (Math.abs((ev.getX() - startpos)) > getWidth() / 4) {
     79                 if (ev.getX() - startpos > 0) {
     80                     /*向左*/
     81                     prevPage();
     82                 } else {
     83                     /*向右*/
     84                     nextPage();
     85                 }
     86             } else {
     87                 /*不变*/
     88                 scrollToPage(1);
     89             }
     90             return true;
     91         }
     92         }
     93         return super.onTouchEvent(ev);
     94     }
     95 
     96     /*完成实例化*/
     97     @Override
     98     protected void onFinishInflate(){
     99         super.onFinishInflate();
    100         Log.i(tag, "onFinishInflate Called!");
    101         init();
    102     }
    103     
    104     /*初始化,加入三个子View*/
    105     private void init(){
    106         this.childGroup=(KamLinearLayout) findViewById(R.id.container);
    107         /*添加LayoutChange监听器*/
    108         childGroup.addKamLayoutChangeListener(listener);
    109         /*调用其自身的LayoutChange监听器(不支持Android2.3)*/
    110         /*childGroup.addOnLayoutChangeListener(listener);*/
    111         
    112         addRight(createExampleView(-1));
    113         addRight(createExampleView(0));
    114         addRight(createExampleView(1));
    115     }
    116     /*添加监听器*/
    117     kamLayoutChangeListener listener = new kamLayoutChangeListener() {
    118         
    119         @Override
    120         public void onLayoutChange() {
    121             // TODO Auto-generated method stub
    122             Log.i(tag, "onLayoutChanged Called!");
    123             scrollToPage(1);
    124         }
    125     };
    126     /*
    127      //注意,如果不需要支持Android2.3,可以将上面的listener替换成下方listener
    128     OnLayoutChangeListener listener = new OnLayoutChangeListener() {
    129         
    130         @Override
    131         public void onLayoutChange(View arg0, int arg1, int arg2, int arg3,
    132                 int arg4, int arg5, int arg6, int arg7, int arg8) {
    133             // TODO Auto-generated method stub
    134             Log.i(tag, "onLayoutChanged Called!");
    135             scrollToPage(1);
    136         }
    137     };
    138     */
    139     
    140     /*左翻页*/
    141     public void prevPage(){
    142         PageNo--;
    143         addLeft(createExampleView(PageNo-1));
    144         removeRight();
    145     }
    146     
    147     /*右翻页*/
    148     public void nextPage(){
    149         PageNo++;
    150         addRight(createExampleView(PageNo+1));
    151         removeLeft();
    152     }
    153     
    154     
    155     /*获取某个孩子的X坐标*/
    156     private int getChildLeft(int index){
    157         if (index>=0 && childGroup != null) {
    158             if(index< childGroup.getChildCount())
    159                 return  childGroup.getChildAt(index).getLeft();
    160         }
    161         return 0;
    162     }
    163     
    164     /**
    165      * 向右边添加View
    166      * @param view 需要添加的View
    167      * @return true添加成功|false添加失败
    168      */
    169     public boolean addRight(View view){
    170         if(view==null || childGroup==null)return false;
    171         childGroup.addView(view);
    172         return true;
    173     }
    174     
    175     /**
    176      * 删除右边的View
    177      * @return true成功|false失败
    178      */
    179     public boolean removeRight(){
    180         if( childGroup==null || childGroup.getChildCount()<=0)return false;
    181         childGroup.removeViewAt(childGroup.getChildCount()-1);
    182         return true;
    183     }
    184     
    185     /**
    186      * 向左边添加View
    187      * @param view 需要添加的View
    188      * @return true添加成功|false添加失败
    189      */
    190     public boolean addLeft(View view){
    191         if(view==null || childGroup==null)return false;
    192         childGroup.addView(view, 0);
    193         
    194         /*因为在左边增加了View,因此所有View的x坐标都会增加,因此需要让ScrollView也跟着移动,才能从屏幕看来保持平滑。*/
    195         int tmpwidth = view.getLayoutParams().width;
    196         if(tmpwidth==0)tmpwidth=getWinWidth();
    197         Log.i(tag, "the new view's width = "+view.getLayoutParams().width);
    198         this.scrollTo(this.getScrollX()+tmpwidth, 0);
    199         
    200         return true;
    201     }
    202 
    203     /**
    204      * 删除左边的View
    205      * @return true成功|false失败
    206      */
    207     public boolean removeLeft(){
    208         if( childGroup==null || childGroup.getChildCount()<=0)return false;
    209         
    210         /*因为在左边删除了View,因此所有View的x坐标都会减少,因此需要让ScrollView也跟着移动。*/
    211         int tmpwidth=childGroup.getChildAt(0).getWidth();
    212         childGroup.removeViewAt(0);
    213         this.scrollTo((int) (this.getScrollX()-tmpwidth), 0);
    214         
    215         return true;
    216     }
    217     
    218     /**
    219      * 跳转到指定的页面
    220      * 
    221      * @param index 跳转的页码
    222      * @return
    223      */
    224     public boolean scrollToPage(int index){
    225         if(childGroup==null)return false;
    226         if(index<0 || index >= childGroup.getChildCount())return false;
    227         smoothScrollTo(getChildLeft(index), 0);
    228         return true;
    229     }
    230     
    231     private int getWinWidth() {
    232         DisplayMetrics dm = new DisplayMetrics();
    233         // 获取屏幕信息
    234         dm = context.getResources().getDisplayMetrics();
    235         return dm.widthPixels;
    236     }
    237 
    238     private int getWinHeight() {
    239         DisplayMetrics dm = new DisplayMetrics();
    240         // 获取屏幕信息
    241         dm = context.getResources().getDisplayMetrics();
    242         return dm.heightPixels;
    243     }
    244     /*生成一个测试用View。真正使用的时候就不需要这个了。*/
    245     private View createExampleView(int index){
    246         LayoutParams params = new LayoutParams(getWinWidth(), getWinHeight());
    247         /*设置不同的背景色使效果更加明显*/
    248         int colorarr[] = {
    249                 Color.rgb(240, 180, 180),
    250                 Color.rgb(240, 240, 180),
    251                 Color.rgb(180, 240, 240),
    252                 Color.rgb(180, 240, 180)};
    253         TextView txtview = new TextView(context);
    254         txtview.setBackgroundColor(colorarr[(index%4+4) % 4]);
    255         txtview.setText(index + "");
    256         txtview.setTextSize(40);
    257         txtview.setGravity(Gravity.CENTER);
    258         txtview.setLayoutParams(params);
    259         
    260         return txtview;
    261     }
    262     
    263     
    264     /*下面的方法仅仅是个人喜好加上的,用于判断用户手指左右滑动的速度。*/
    265     private void initSpeedChange(int x){
    266         if(poscache.length<=1)return;
    267         poscache[0]=1;
    268         for(int i=1;i<poscache.length;i++){
    269             
    270         }
    271     }
    272     private void movingSpeedChange(int x){
    273         poscache[0]%=poscache.length-1;
    274         poscache[0]++;
    275         //Log.i(tag, "touch speed:"+(x-poscache[poscache[0]]));
    276         poscache[poscache[0]]=x;
    277     }
    278     private int releaseSpeedChange(int x){
    279         return releaseSpeedChange(x, 30);
    280     }
    281     private int releaseSpeedChange(int x,int limit){
    282         poscache[0]%=poscache.length-1;
    283         poscache[0]++;
    284         /*检测到向左的速度很大*/
    285         if(poscache[poscache[0]]-x>limit)return 1;
    286         /*检测到向右的速度很大*/
    287         if(x-poscache[poscache[0]]>limit)return -1;
    288         
    289         return 0;
    290     }
    291 }
    复制代码

     KamLinearLayout.java (如果不需要支持APILevel 10及以下,可以无视这个类)

    复制代码
     1 package com.kam.horizontalscrollviewtest.view;
     2 
     3 import android.content.Context;
     4 import android.util.AttributeSet;
     5 import android.widget.LinearLayout;
     6 
     7 public class KamLinearLayout extends LinearLayout {
     8     kamLayoutChangeListener listener = null;
     9     
    10     public void addKamLayoutChangeListener(kamLayoutChangeListener listener){
    11         this.listener=listener;
    12     }
    13     
    14     public KamLinearLayout(Context context) {
    15         super(context);
    16         // TODO Auto-generated constructor stub
    17     }
    18     public KamLinearLayout(Context context, AttributeSet attrs) {
    19         super(context, attrs);
    20         // TODO Auto-generated constructor stub
    21     }
    22 
    23     
    24     @Override
    25     public void onLayout(boolean changed,
    26             int l, int t, int r, int b){
    27         super.onLayout(changed, l, t, r, b);
    28         if(this.listener!=null)this.listener.onLayoutChange();
    29     }
    30 
    31 }
    32 /*自定义监听器*/
    33 interface kamLayoutChangeListener{
    34     abstract void onLayoutChange();
    35 
    36 }
    复制代码

    MainActivity.java

    复制代码
     1 package com.kam.horizontalscrollviewtest;
     2 
     3 import android.support.v7.app.ActionBarActivity;
     4 import android.os.Bundle;
     5 import android.view.Menu;
     6 import android.view.MenuItem;
     7 
     8 public class MainActivity extends ActionBarActivity {
     9 
    10     @Override
    11     protected void onCreate(Bundle savedInstanceState) {
    12         super.onCreate(savedInstanceState);
    13         setContentView(R.layout.kamhsview);
    14     }
    15 
    16     @Override
    17     public boolean onCreateOptionsMenu(Menu menu) {
    18         // Inflate the menu; this adds items to the action bar if it is present.
    19         getMenuInflater().inflate(R.menu.main, menu);
    20         return true;
    21     }
    22 
    23     @Override
    24     public boolean onOptionsItemSelected(MenuItem item) {
    25         // Handle action bar item clicks here. The action bar will
    26         // automatically handle clicks on the Home/Up button, so long
    27         // as you specify a parent activity in AndroidManifest.xml.
    28         int id = item.getItemId();
    29         if (id == R.id.action_settings) {
    30             return true;
    31         }
    32         return super.onOptionsItemSelected(item);
    33     }
    34 }
    复制代码

    kamhsview.xml

    复制代码
    <?xml version="1.0" encoding="utf-8"?>
    <com.kam.horizontalscrollviewtest.view.KamHorizontalScrollView
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/kamscrollview"
            android:fadingEdge="none"
            android:scrollbars="none"
            >
            
        <!-- 如果你不需要支持Android2.3,可以把后面的KamLinearLayout替换成普通的LinearLayout
        <LinearLayout
            android:id="@+id/container1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal" >
            
        </LinearLayout > -->
        <com.kam.horizontalscrollviewtest.view.KamLinearLayout
            android:id="@+id/container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal">
            
        </com.kam.horizontalscrollviewtest.view.KamLinearLayout>
    </com.kam.horizontalscrollviewtest.view.KamHorizontalScrollView>
    复制代码


    AndroidManifest就是默认的那个,没有改。

    复制代码
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.kam.horizontalscrollviewtest"
        android:versionCode="1"
        android:versionName="1.0" >
    
        <uses-sdk
            android:minSdkVersion="8"
            android:targetSdkVersion="10" />
    
        <application
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name"
            android:theme="@style/AppBaseTheme" >
            <activity
                android:name=".MainActivity"
                android:label="@string/app_name" >
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    
    </manifest>
    复制代码
  • 相关阅读:
    [PHP]搭建Laravel心路历程
    [python爬虫]爬取贴吧某页美女图片+爬取糗百美女图片
    [Python2.7]python关于sys.agrv的使用
    [渗透测试]Sqlmap使用参数说明
    摘录的关于代码维护性的文章片段
    Python文本处理
    增加layer---待完成
    Python小程序——线性时间排序
    Python小程序——快排算法
    Python学习——多进程学习
  • 原文地址:https://www.cnblogs.com/dongweiq/p/3962523.html
Copyright © 2011-2022 走看看