ViewPager 可左右滑动和缩放的图片浏览

最近因为要做一个项目,需要使用到图片的浏览。我就自己在网上找了些资料,然后加以修改整理后出来一个demo,希望可以帮助到需要的人。同时这也是我第一个技术博客。

在做之前首先需要了解一下什么是ViewPager,怎么使用ViewPager。我这里提供一篇文章给大家  http://www.2cto.com/kf/201411/353975.html   我这里不在赘述了。

好了  了解完可以开始了

 PS  我不知道怎么制作那种动态的效果图,如果有谁知道请告诉我  我将万分感谢

 

 

 

一步一步来

 

首先先要写个布局 ViewPager的布局   activity_main.xml

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
 3     android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
 4     android:paddingRight="@dimen/activity_horizontal_margin"
 5     android:paddingTop="@dimen/activity_vertical_margin"
 6     android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"
 7     android:orientation="vertical">
 8 
 9     <LinearLayout
10         android:id="@+id/linearlayout"
11         android:layout_width="300dp"
12         android:layout_height="300dp"
13         android:background="@drawable/bg_common_frames"
14         android:layout_gravity="center"
15         >
16 
17         <android.support.v4.view.ViewPager
18             android:id="@+id/viewpager"
19             android:layout_width="wrap_content"
20             android:layout_height="wrap_content"
21             android:layout_gravity="center"
22             />
23 
24     </LinearLayout>
25 
26 
27 
28 </LinearLayout>

对应的效果是这样的 请看 有点丑 不管啦。用了一张底图  在上面代码中的 LinearLayout

 

然后是图片详情布局     image_details.xml

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent">
 5 
 6     <android.support.v4.view.ViewPager
 7         android:id="@+id/view_pager"
 8         android:layout_width="match_parent"
 9         android:layout_height="match_parent">
10     </android.support.v4.view.ViewPager>
11 
12     <TextView
13         android:id="@+id/page_text"
14         android:layout_width="wrap_content"
15         android:layout_height="wrap_content"
16         android:layout_alignParentBottom="true"
17         android:layout_centerHorizontal="true"
18         android:layout_marginBottom="10dp"
19         android:textColor="#fff"
20         android:textSize="18sp" />
21 
22 </RelativeLayout>

 

最后一个布局是缩放的布局  zoom_image_layout.xml

1 <?xml version="1.0" encoding="utf-8"?>
2 <com.higgs.mviewpager.ZoomImageView xmlns:android="http://schemas.android.com/apk/res/android"
3     android:id="@+id/zoom_image_view"
4     android:layout_width="match_parent"
5     android:layout_height="match_parent"
6     android:background="#000000" >
7 
8 </com.higgs.mviewpager.ZoomImageView>

 

 

 

 

 

 

基本的布局我们完成了

 

再看代码吧

 

我这里因为迎合大部分朋友的需求 ,改成了从网络上下载图片的方式作为图片来源

先模拟一个图片URL的类

 1 package com.higgs.mviewpager;
 2 
 3 public class Images {
 4 
 5     public final static String[] imageUrls = new String[]{
 6             "https://img-my.csdn.net/uploads/201309/01/1378037235_3453.jpg",
 7             "https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg",
 8             "https://img-my.csdn.net/uploads/201309/01/1378037235_9280.jpg",
 9             "https://img-my.csdn.net/uploads/201309/01/1378037234_3539.jpg",
10             "https://img-my.csdn.net/uploads/201309/01/1378037234_6318.jpg",
11     };
12 }

 

然后就是实现代码了

说一下思路, 先获取ViewPager 然后往ViewPager 添加图片,但是图片是网络上的 所以要先下载后在添加进去,这里处于防止加载比较多的图片时造成OOM问题 所以引入了 LruCache 类缓存技术。先讲下载的图片存入手机里并缓存下来,一为了节省用户的数据流量,二为了之后的加载速度。有了缓冲下次就不用在重复去网络上下载了。  代码如下

  1 package com.higgs.mviewpager;
  2 
  3 import android.app.Activity;
  4 import android.content.Intent;
  5 import android.graphics.Bitmap;
  6 import android.graphics.BitmapFactory;
  7 import android.os.AsyncTask;
  8 import android.os.Bundle;
  9 import android.os.Environment;
 10 import android.support.v4.view.PagerAdapter;
 11 import android.support.v4.view.ViewPager;
 12 import android.util.Log;
 13 import android.view.View;
 14 import android.view.ViewGroup;
 15 import android.widget.ImageView;
 16 import android.widget.Toast;
 17 
 18 import java.io.BufferedInputStream;
 19 import java.io.BufferedOutputStream;
 20 import java.io.BufferedReader;
 21 import java.io.File;
 22 import java.io.FileOutputStream;
 23 import java.io.IOException;
 24 import java.io.InputStream;
 25 import java.io.InputStreamReader;
 26 import java.net.HttpURLConnection;
 27 import java.net.URL;
 28 import java.util.ArrayList;
 29 
 30 
 31 public class MainActivity extends Activity {
 32 
 33     private static final String TAG = "MainActivity";
 34     private ImageLoader  imageLoader = ImageLoader.getInstance();  //获取图片进行管理的工具类实例。
 35 
 36     private ViewPager viewpager;
 37     private ArrayList<View> viewList;
 38 //    private ImageView imageView1;
 39 //    private ImageView imageView2;
 40 //    private ImageView imageView3;
 41     @Override
 42     protected void onCreate(Bundle savedInstanceState) {
 43         super.onCreate(savedInstanceState);
 44         setContentView(R.layout.activity_main);
 45         initView();
 46     }
 47 
 48 
 49     private void initView() {
 50         viewpager = (ViewPager) findViewById(R.id.viewpager); //获取viewpager
 51         viewList = new ArrayList<View>();   //保存view,用于PagerAdapter
 52         for(int i = 0; i<Images.imageUrls.length; i++){
 53             new DownLoadPic().execute(i);//图片有几张就下载几张
 54         }
 55 
 56 
 57 
 58 //
 59 //        imageView1 = new ImageView(this);
 60 //        imageView2 = new ImageView(this);
 61 //        imageView3 = new ImageView(this);
 62 //        imageView1.setImageResource(R.drawable.b);
 63 //        imageView2.setImageResource(R.drawable.c);
 64 //        imageView3.setImageResource(R.drawable.d);
 65 //
 66 //        viewList.add(imageView1);
 67 //        viewList.add(imageView2);
 68 //        viewList.add(imageView3);
 69 
 70         viewpager.setAdapter(pagerAdapter);  //加入适配器
 71         Log.e("TAG1", "" + viewList.size());
 72 
 73     }
 74 
 75 
 76     /**
 77      * 图片异步下载内部类
 78      */
 79 
 80     class DownLoadPic extends AsyncTask<Integer,Void,Bitmap>{
 81 
 82         /**
 83          * 记录每个图片对应的位置
 84          */
 85         private int mposition;
 86 
 87         @Override
 88         protected Bitmap doInBackground(Integer... params) {
 89 
 90             mposition = params[0];//获取传过来的图片position (下标)
 91             String strurl = Images.imageUrls[mposition];  //通过下标获得图片URL
 92             File imageFile = new File(getImagePath(strurl)); //获取图片在本地手机中的位置路径
 93             if (!imageFile.exists()) {  //判断是否存在手机里
 94                 doPost(strurl);//如果没有就下载图片
 95             }
 96             if (strurl != null) {
 97                 Bitmap bitmap = ImageLoader.decodeSampledBitmapFromResource(imageFile.getPath(),
 98                         290);  //压缩图片  我这里写的是290
 99                 if (bitmap != null) {
100                     imageLoader.addBitmapToMemoryCache(strurl, bitmap); //将图片加入缓冲  LruCache中
101                     return bitmap;
102                 }
103             }
104 
105 
106             return null;
107         }
108 
109         @Override
110         protected void onPostExecute(Bitmap o) {
111             ImageView imageView = new ImageView(MainActivity.this);
112             imageView.setImageBitmap(o);
113             imageView.setOnClickListener(new View.OnClickListener() {
114                 @Override
115                 public void onClick(View v) {
116 
117                     Intent intent = new Intent(MainActivity.this, ImageDetailsActivity.class);//打开图片详情类
118                     intent.putExtra("image_position", mposition);
119                     MainActivity.this.startActivity(intent);
120                 }
121             });
122 
123             viewList.add(imageView);
124             pagerAdapter.notifyDataSetChanged();  //这句话一定不能少  ,不然会有异常
125             Log.e("TAG2", "" + viewList.size());
126 
127 
128         }
129 
130 
131     }
132 
133     /**
134      * ViewPager的适配器  重写下面几个方法就可以了
135      */
136 
137     PagerAdapter pagerAdapter = new PagerAdapter() {
138 
139         @Override
140         public int getCount() {
141 
142             return viewList.size();
143         }
144 
145         @Override
146         public boolean isViewFromObject(View view, Object object) {
147             return view == object;
148         }
149 
150         @Override
151         public void destroyItem(ViewGroup container, int position,
152                                 Object object) {
153             container.removeView(viewList.get(position));
154 
155         }
156 
157         @Override
158         public int getItemPosition(Object object) {
159 
160             return super.getItemPosition(object);
161         }
162 
163         @Override
164         public Object instantiateItem(ViewGroup container, int position) {
165             container.addView(viewList.get(position));
166             return viewList.get(position);
167         }
168 
169     };
170 
171 
172     /**
173      * 下载图片方法  并将图片缓冲至手机指定位置中
174      * @param urlstr  图片URL
175      */
176     public void doPost(String urlstr){
177         if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
178             Log.d("TAG", "monted sdcard");
179         } else {
180             Log.d("TAG", "has no sdcard");
181         }
182         HttpURLConnection con = null;
183         FileOutputStream fos = null;
184         BufferedOutputStream bos = null;
185         BufferedInputStream bis = null;
186         File imageFile = null;
187         try {
188             URL url = new URL(urlstr);
189             con = (HttpURLConnection) url.openConnection();
190             con.setConnectTimeout(5 * 1000);
191             con.setReadTimeout(15 * 1000);
192             con.setDoInput(true);
193             con.setDoOutput(true);
194             bis = new BufferedInputStream(con.getInputStream());
195             imageFile = new File(getImagePath(urlstr));
196             fos = new FileOutputStream(imageFile);
197             bos = new BufferedOutputStream(fos);
198             byte[] b = new byte[1024];
199             int length;
200             while ((length = bis.read(b)) != -1) {// 写入手机中
201                 bos.write(b, 0, length);
202                 bos.flush();
203             }
204         } catch (Exception e) {
205             e.printStackTrace();
206         } finally {
207             try {
208                 if (bis != null) {
209                     bis.close();
210                 }
211                 if (bos != null) {
212                     bos.close();
213                 }
214                 if (con != null) {
215                     con.disconnect();
216                 }
217             } catch (IOException e) {
218                 e.printStackTrace();
219             }
220         }
221         if (imageFile != null) {
222             Bitmap bitmap = ImageLoader.decodeSampledBitmapFromResource(imageFile.getPath(),
223                     290);
224             if (bitmap != null) {
225                 imageLoader.addBitmapToMemoryCache(urlstr, bitmap);
226             }
227         }
228     }
229 
230     /**
231      * 获取图片的本地存储路径。
232      *
233      * @param imageUrl
234      *            图片的URL地址。
235      * @return 图片的本地存储路径。
236      */
237     private String getImagePath(String imageUrl) {
238         int lastSlashIndex = imageUrl.lastIndexOf("/");
239         String imageName = imageUrl.substring(lastSlashIndex + 1);
240         String imageDir = Environment.getExternalStorageDirectory().getPath()
241                 + "/pwxceshibao/";
242         File file = new File(imageDir);
243         if (!file.exists()) {
244             file.mkdirs();
245         }
246         String imagePath = imageDir + imageName;
247         return imagePath;
248     }
249 
250 }

 

下面是几个工具类。是我从网上找的。个人觉得非常有用。所以就拿过来了。注意了,下面几个工具类都用上了,所以不能少哦

在这里要感谢 http://blog.csdn.net/sziicool/article/details/18728187  博主,我是站在他的肩膀上修改的。

 

 

第一个是查看大图类  就是图片详情类

  1 package com.higgs.mviewpager;
  2 
  3 import android.app.Activity;
  4 import android.graphics.Bitmap;
  5 import android.graphics.BitmapFactory;
  6 import android.os.Bundle;
  7 import android.os.Environment;
  8 import android.support.v4.view.PagerAdapter;
  9 import android.support.v4.view.ViewPager;
 10 import android.support.v4.view.ViewPager.OnPageChangeListener;
 11 import android.view.LayoutInflater;
 12 import android.view.View;
 13 import android.view.ViewGroup;
 14 import android.view.Window;
 15 import android.widget.TextView;
 16 
 17 import java.io.File;
 18 
 19 /**
 20  * 查看大图的Activity界面。
 21  * 
 22  * @author guolin
 23  */
 24 public class ImageDetailsActivity extends Activity implements
 25         OnPageChangeListener {
 26 
 27     /**
 28      * 用于管理图片的滑动
 29      */
 30     private ViewPager viewPager;
 31 
 32     /**
 33      * 显示当前图片的页数
 34      */
 35     private TextView pageText;
 36 
 37     @Override
 38     protected void onCreate(Bundle savedInstanceState) {
 39         super.onCreate(savedInstanceState);
 40         requestWindowFeature(Window.FEATURE_NO_TITLE);
 41         setContentView(R.layout.image_details);
 42         int imagePosition = getIntent().getIntExtra("image_position", 0);
 43         pageText = (TextView) findViewById(R.id.page_text);
 44         viewPager = (ViewPager) findViewById(R.id.view_pager);
 45         ViewPagerAdapter adapter = new ViewPagerAdapter();
 46         viewPager.setAdapter(adapter);
 47         viewPager.setCurrentItem(imagePosition);
 48         viewPager.setOnPageChangeListener(this);
 49         viewPager.setEnabled(false);
 50         // 设定当前的页数和总页数
 51         pageText.setText((imagePosition + 1) + "/" + Images.imageUrls.length);
 52     }
 53 
 54     /**
 55      * ViewPager的适配器
 56      * 
 57      * @author guolin
 58      */
 59     class ViewPagerAdapter extends PagerAdapter {
 60 
 61         @Override
 62         public Object instantiateItem(ViewGroup container, int position) {
 63             String imagePath = getImagePath(Images.imageUrls[position]);
 64             Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
 65             if (bitmap == null) {
 66                 bitmap = BitmapFactory.decodeResource(getResources(),
 67                         R.drawable.empty_photo);
 68             }
 69             View view = LayoutInflater.from(ImageDetailsActivity.this).inflate(
 70                     R.layout.zoom_image_layout, null);
 71             ZoomImageView zoomImageView = (ZoomImageView) view
 72                     .findViewById(R.id.zoom_image_view);
 73             zoomImageView.setImageBitmap(bitmap);
 74             container.addView(view);
 75             return view;
 76         }
 77 
 78         @Override
 79         public int getCount() {
 80             return Images.imageUrls.length;
 81         }
 82 
 83         @Override
 84         public boolean isViewFromObject(View arg0, Object arg1) {
 85             return arg0 == arg1;
 86         }
 87 
 88         @Override
 89         public void destroyItem(ViewGroup container, int position, Object object) {
 90             View view = (View) object;
 91             container.removeView(view);
 92         }
 93 
 94     }
 95 
 96     /**
 97      * 获取图片的本地存储路径。
 98      * 
 99      * @param imageUrl
100      *            图片的URL地址。
101      * @return 图片的本地存储路径。
102      */
103     private String getImagePath(String imageUrl) {
104         int lastSlashIndex = imageUrl.lastIndexOf("/");
105         String imageName = imageUrl.substring(lastSlashIndex + 1);
106         String imageDir = Environment.getExternalStorageDirectory().getPath()
107                 + "/pwxceshibao/";
108         File file = new File(imageDir);
109         if (!file.exists()) {
110             file.mkdirs();
111         }
112         String imagePath = imageDir + imageName;
113         return imagePath;
114     }
115 
116     @Override
117     public void onPageScrollStateChanged(int arg0) {
118 
119     }
120 
121     @Override
122     public void onPageScrolled(int arg0, float arg1, int arg2) {
123 
124     }
125 
126     @Override
127     public void onPageSelected(int currentPage) {
128         // 每当页数发生改变时重新设定一遍当前的页数和总页数
129         pageText.setText((currentPage + 1) + "/" + Images.imageUrls.length);
130     }
131 
132 }

第二个是图片缩放工具类

  1 package com.higgs.mviewpager;
  2 
  3 import android.content.Context;
  4 import android.graphics.Bitmap;
  5 import android.graphics.Canvas;
  6 import android.graphics.Matrix;
  7 import android.util.AttributeSet;
  8 import android.view.MotionEvent;
  9 import android.view.View;
 10 
 11 /**
 12  * 自定义的ImageView控制,可对图片进行多点触控缩放和拖动
 13  * 
 14  * @author guolin
 15  */
 16 public class ZoomImageView extends View {
 17 
 18     /**
 19      * 初始化状态常量
 20      */
 21     public static final int STATUS_INIT = 1;
 22 
 23     /**
 24      * 图片放大状态常量
 25      */
 26     public static final int STATUS_ZOOM_OUT = 2;
 27 
 28     /**
 29      * 图片缩小状态常量
 30      */
 31     public static final int STATUS_ZOOM_IN = 3;
 32 
 33     /**
 34      * 图片拖动状态常量
 35      */
 36     public static final int STATUS_MOVE = 4;
 37 
 38     /**
 39      * 用于对图片进行移动和缩放变换的矩阵
 40      */
 41     private Matrix matrix = new Matrix();
 42 
 43     /**
 44      * 待展示的Bitmap对象
 45      */
 46     private Bitmap sourceBitmap;
 47 
 48     /**
 49      * 记录当前操作的状态,可选值为STATUS_INIT、STATUS_ZOOM_OUT、STATUS_ZOOM_IN和STATUS_MOVE
 50      */
 51     private int currentStatus;
 52 
 53     /**
 54      * ZoomImageView控件的宽度
 55      */
 56     private int width;
 57 
 58     /**
 59      * ZoomImageView控件的高度
 60      */
 61     private int height;
 62 
 63     /**
 64      * 记录两指同时放在屏幕上时,中心点的横坐标值
 65      */
 66     private float centerPointX;
 67 
 68     /**
 69      * 记录两指同时放在屏幕上时,中心点的纵坐标值
 70      */
 71     private float centerPointY;
 72 
 73     /**
 74      * 记录当前图片的宽度,图片被缩放时,这个值会一起变动
 75      */
 76     private float currentBitmapWidth;
 77 
 78     /**
 79      * 记录当前图片的高度,图片被缩放时,这个值会一起变动
 80      */
 81     private float currentBitmapHeight;
 82 
 83     /**
 84      * 记录上次手指移动时的横坐标
 85      */
 86     private float lastXMove = -1;
 87 
 88     /**
 89      * 记录上次手指移动时的纵坐标
 90      */
 91     private float lastYMove = -1;
 92 
 93     /**
 94      * 记录手指在横坐标方向上的移动距离
 95      */
 96     private float movedDistanceX;
 97 
 98     /**
 99      * 记录手指在纵坐标方向上的移动距离
100      */
101     private float movedDistanceY;
102 
103     /**
104      * 记录图片在矩阵上的横向偏移值
105      */
106     private float totalTranslateX;
107 
108     /**
109      * 记录图片在矩阵上的纵向偏移值
110      */
111     private float totalTranslateY;
112 
113     /**
114      * 记录图片在矩阵上的总缩放比例
115      */
116     private float totalRatio;
117 
118     /**
119      * 记录手指移动的距离所造成的缩放比例
120      */
121     private float scaledRatio;
122 
123     /**
124      * 记录图片初始化时的缩放比例
125      */
126     private float initRatio;
127 
128     /**
129      * 记录上次两指之间的距离
130      */
131     private double lastFingerDis;
132 
133     /**
134      * ZoomImageView构造函数,将当前操作状态设为STATUS_INIT。
135      * 
136      * @param context
137      * @param attrs
138      */
139     public ZoomImageView(Context context, AttributeSet attrs) {
140         super(context, attrs);
141         currentStatus = STATUS_INIT;
142     }
143 
144     /**
145      * 将待展示的图片设置进来。
146      * 
147      * @param bitmap
148      *            待展示的Bitmap对象
149      */
150     public void setImageBitmap(Bitmap bitmap) {
151         sourceBitmap = bitmap;
152         invalidate();
153     }
154 
155     @Override
156     protected void onLayout(boolean changed, int left, int top, int right,
157             int bottom) {
158         super.onLayout(changed, left, top, right, bottom);
159         if (changed) {
160             // 分别获取到ZoomImageView的宽度和高度
161             width = getWidth();
162             height = getHeight();
163         }
164     }
165 
166     @Override
167     public boolean onTouchEvent(MotionEvent event) {
168         if (initRatio == totalRatio) {
169             getParent().requestDisallowInterceptTouchEvent(false);
170         } else {
171             getParent().requestDisallowInterceptTouchEvent(true);
172         }
173         switch (event.getActionMasked()) {
174         case MotionEvent.ACTION_POINTER_DOWN:
175             if (event.getPointerCount() == 2) {
176                 // 当有两个手指按在屏幕上时,计算两指之间的距离
177                 lastFingerDis = distanceBetweenFingers(event);
178             }
179             break;
180         case MotionEvent.ACTION_CANCEL:
181         case MotionEvent.ACTION_MOVE:
182             if (event.getPointerCount() == 1) {
183                 // 只有单指按在屏幕上移动时,为拖动状态
184                 float xMove = event.getX();
185                 float yMove = event.getY();
186                 if (lastXMove == -1 && lastYMove == -1) {
187                     lastXMove = xMove;
188                     lastYMove = yMove;
189                 }
190                 currentStatus = STATUS_MOVE;
191                 movedDistanceX = xMove - lastXMove;
192                 movedDistanceY = yMove - lastYMove;
193                 // 进行边界检查,不允许将图片拖出边界
194                 if (totalTranslateX + movedDistanceX > 0) {
195                     movedDistanceX = 0;
196                 } else if (width - (totalTranslateX + movedDistanceX) > currentBitmapWidth) {
197                     movedDistanceX = 0;
198                 }
199                 if (totalTranslateY + movedDistanceY > 0) {
200                     movedDistanceY = 0;
201                 } else if (height - (totalTranslateY + movedDistanceY) > currentBitmapHeight) {
202                     movedDistanceY = 0;
203                 }
204                 // 调用onDraw()方法绘制图片
205                 invalidate();
206                 lastXMove = xMove;
207                 lastYMove = yMove;
208             } else if (event.getPointerCount() == 2) {
209                 // 有两个手指按在屏幕上移动时,为缩放状态
210                 centerPointBetweenFingers(event);
211                 double fingerDis = distanceBetweenFingers(event);
212                 if (fingerDis > lastFingerDis) {
213                     currentStatus = STATUS_ZOOM_OUT;
214                 } else {
215                     currentStatus = STATUS_ZOOM_IN;
216                 }
217                 // 进行缩放倍数检查,最大只允许将图片放大4倍,最小可以缩小到初始化比例
218                 if ((currentStatus == STATUS_ZOOM_OUT && totalRatio < 4 * initRatio)
219                         || (currentStatus == STATUS_ZOOM_IN && totalRatio > initRatio)) {
220                     scaledRatio = (float) (fingerDis / lastFingerDis);
221                     totalRatio = totalRatio * scaledRatio;
222                     if (totalRatio > 4 * initRatio) {
223                         totalRatio = 4 * initRatio;
224                     } else if (totalRatio < initRatio) {
225                         totalRatio = initRatio;
226                     }
227                     // 调用onDraw()方法绘制图片
228                     invalidate();
229                     lastFingerDis = fingerDis;
230                 }
231             }
232             break;
233         case MotionEvent.ACTION_POINTER_UP:
234             if (event.getPointerCount() == 2) {
235                 // 手指离开屏幕时将临时值还原
236                 lastXMove = -1;
237                 lastYMove = -1;
238             }
239             break;
240         case MotionEvent.ACTION_UP:
241             // 手指离开屏幕时将临时值还原
242             lastXMove = -1;
243             lastYMove = -1;
244             break;
245         default:
246             break;
247         }
248         return true;
249     }
250 
251     /**
252      * 根据currentStatus的值来决定对图片进行什么样的绘制操作。
253      */
254     @Override
255     protected void onDraw(Canvas canvas) {
256         super.onDraw(canvas);
257         switch (currentStatus) {
258         case STATUS_ZOOM_OUT:
259         case STATUS_ZOOM_IN:
260             zoom(canvas);
261             break;
262         case STATUS_MOVE:
263             move(canvas);
264             break;
265         case STATUS_INIT:
266             initBitmap(canvas);
267         default:
268             if (sourceBitmap != null) {
269                 canvas.drawBitmap(sourceBitmap, matrix, null);
270             }
271             break;
272         }
273     }
274 
275     /**
276      * 对图片进行缩放处理。
277      * 
278      * @param canvas
279      */
280     private void zoom(Canvas canvas) {
281         matrix.reset();
282         // 将图片按总缩放比例进行缩放
283         matrix.postScale(totalRatio, totalRatio);
284         float scaledWidth = sourceBitmap.getWidth() * totalRatio;
285         float scaledHeight = sourceBitmap.getHeight() * totalRatio;
286         float translateX = 0f;
287         float translateY = 0f;
288         // 如果当前图片宽度小于屏幕宽度,则按屏幕中心的横坐标进行水平缩放。否则按两指的中心点的横坐标进行水平缩放
289         if (currentBitmapWidth < width) {
290             translateX = (width - scaledWidth) / 2f;
291         } else {
292             translateX = totalTranslateX * scaledRatio + centerPointX
293                     * (1 - scaledRatio);
294             // 进行边界检查,保证图片缩放后在水平方向上不会偏移出屏幕
295             if (translateX > 0) {
296                 translateX = 0;
297             } else if (width - translateX > scaledWidth) {
298                 translateX = width - scaledWidth;
299             }
300         }
301         // 如果当前图片高度小于屏幕高度,则按屏幕中心的纵坐标进行垂直缩放。否则按两指的中心点的纵坐标进行垂直缩放
302         if (currentBitmapHeight < height) {
303             translateY = (height - scaledHeight) / 2f;
304         } else {
305             translateY = totalTranslateY * scaledRatio + centerPointY
306                     * (1 - scaledRatio);
307             // 进行边界检查,保证图片缩放后在垂直方向上不会偏移出屏幕
308             if (translateY > 0) {
309                 translateY = 0;
310             } else if (height - translateY > scaledHeight) {
311                 translateY = height - scaledHeight;
312             }
313         }
314         // 缩放后对图片进行偏移,以保证缩放后中心点位置不变
315         matrix.postTranslate(translateX, translateY);
316         totalTranslateX = translateX;
317         totalTranslateY = translateY;
318         currentBitmapWidth = scaledWidth;
319         currentBitmapHeight = scaledHeight;
320         canvas.drawBitmap(sourceBitmap, matrix, null);
321     }
322 
323     /**
324      * 对图片进行平移处理
325      * 
326      * @param canvas
327      */
328     private void move(Canvas canvas) {
329         matrix.reset();
330         // 根据手指移动的距离计算出总偏移值
331         float translateX = totalTranslateX + movedDistanceX;
332         float translateY = totalTranslateY + movedDistanceY;
333         // 先按照已有的缩放比例对图片进行缩放
334         matrix.postScale(totalRatio, totalRatio);
335         // 再根据移动距离进行偏移
336         matrix.postTranslate(translateX, translateY);
337         totalTranslateX = translateX;
338         totalTranslateY = translateY;
339         canvas.drawBitmap(sourceBitmap, matrix, null);
340     }
341 
342     /**
343      * 对图片进行初始化操作,包括让图片居中,以及当图片大于屏幕宽高时对图片进行压缩。
344      * 
345      * @param canvas
346      */
347     private void initBitmap(Canvas canvas) {
348         if (sourceBitmap != null) {
349             matrix.reset();
350             int bitmapWidth = sourceBitmap.getWidth();
351             int bitmapHeight = sourceBitmap.getHeight();
352             if (bitmapWidth > width || bitmapHeight > height) {
353                 if (bitmapWidth - width > bitmapHeight - height) {
354                     // 当图片宽度大于屏幕宽度时,将图片等比例压缩,使它可以完全显示出来
355                     float ratio = width / (bitmapWidth * 1.0f);
356                     matrix.postScale(ratio, ratio);
357                     float translateY = (height - (bitmapHeight * ratio)) / 2f;
358                     // 在纵坐标方向上进行偏移,以保证图片居中显示
359                     matrix.postTranslate(0, translateY);
360                     totalTranslateY = translateY;
361                     totalRatio = initRatio = ratio;
362                 } else {
363                     // 当图片高度大于屏幕高度时,将图片等比例压缩,使它可以完全显示出来
364                     float ratio = height / (bitmapHeight * 1.0f);
365                     matrix.postScale(ratio, ratio);
366                     float translateX = (width - (bitmapWidth * ratio)) / 2f;
367                     // 在横坐标方向上进行偏移,以保证图片居中显示
368                     matrix.postTranslate(translateX, 0);
369                     totalTranslateX = translateX;
370                     totalRatio = initRatio = ratio;
371                 }
372                 currentBitmapWidth = bitmapWidth * initRatio;
373                 currentBitmapHeight = bitmapHeight * initRatio;
374             } else {
375                 // 当图片的宽高都小于屏幕宽高时,直接让图片居中显示
376                 float translateX = (width - sourceBitmap.getWidth()) / 2f;
377                 float translateY = (height - sourceBitmap.getHeight()) / 2f;
378                 matrix.postTranslate(translateX, translateY);
379                 totalTranslateX = translateX;
380                 totalTranslateY = translateY;
381                 totalRatio = initRatio = 1f;
382                 currentBitmapWidth = bitmapWidth;
383                 currentBitmapHeight = bitmapHeight;
384             }
385             canvas.drawBitmap(sourceBitmap, matrix, null);
386         }
387     }
388 
389     /**
390      * 计算两个手指之间的距离。
391      * 
392      * @param event
393      * @return 两个手指之间的距离
394      */
395     private double distanceBetweenFingers(MotionEvent event) {
396         float disX = Math.abs(event.getX(0) - event.getX(1));
397         float disY = Math.abs(event.getY(0) - event.getY(1));
398         return Math.sqrt(disX * disX + disY * disY);
399     }
400 
401     /**
402      * 计算两个手指之间中心点的坐标。
403      * 
404      * @param event
405      */
406     private void centerPointBetweenFingers(MotionEvent event) {
407         float xPoint0 = event.getX(0);
408         float yPoint0 = event.getY(0);
409         float xPoint1 = event.getX(1);
410         float yPoint1 = event.getY(1);
411         centerPointX = (xPoint0 + xPoint1) / 2;
412         centerPointY = (yPoint0 + yPoint1) / 2;
413     }
414 
415 }

最后一个是图片管理工具类  也是缓存核心类   这个就是上面提到的防止加载过多的图片时产生OOM

  1 package com.higgs.mviewpager;
  2 
  3 import android.graphics.Bitmap;
  4 import android.graphics.BitmapFactory;
  5 import android.util.LruCache;
  6 
  7 /**
  8  * 对图片进行管理的工具类。
  9  * 
 10  * @author Tony
 11  */
 12 public class ImageLoader {
 13 
 14     /**
 15      * 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。
 16      */
 17     private static LruCache<String, Bitmap> mMemoryCache;
 18 
 19     /**
 20      * ImageLoader的实例。
 21      */
 22     private static ImageLoader mImageLoader;
 23 
 24     private ImageLoader() {
 25         // 获取应用程序最大可用内存
 26         int maxMemory = (int) Runtime.getRuntime().maxMemory();
 27         int cacheSize = maxMemory / 8;
 28         // 设置图片缓存大小为程序最大可用内存的1/8
 29         mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
 30             @Override
 31             protected int sizeOf(String key, Bitmap bitmap) {
 32                 return bitmap.getByteCount();
 33             }
 34         };
 35     }
 36 
 37     /**
 38      * 获取ImageLoader的实例。
 39      * 
 40      * @return ImageLoader的实例。
 41      */
 42     public static ImageLoader getInstance() {
 43         if (mImageLoader == null) {
 44             mImageLoader = new ImageLoader();
 45         }
 46         return mImageLoader;
 47     }
 48 
 49     /**
 50      * 将一张图片存储到LruCache中。
 51      * 
 52      * @param key
 53      *            LruCache的键,这里传入图片的URL地址。
 54      * @param bitmap
 55      *            LruCache的键,这里传入从网络上下载的Bitmap对象。
 56      */
 57     public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
 58         if (getBitmapFromMemoryCache(key) == null) {
 59             mMemoryCache.put(key, bitmap);
 60         }
 61     }
 62 
 63     /**
 64      * 从LruCache中获取一张图片,如果不存在就返回null。
 65      * 
 66      * @param key
 67      *            LruCache的键,这里传入图片的URL地址。
 68      * @return 对应传入键的Bitmap对象,或者null。
 69      */
 70     public Bitmap getBitmapFromMemoryCache(String key) {
 71         return mMemoryCache.get(key);
 72     }
 73 
 74     public static int calculateInSampleSize(BitmapFactory.Options options,
 75             int reqWidth) {
 76         // 源图片的宽度
 77         final int width = options.outWidth;
 78         int inSampleSize = 1;
 79         if (width > reqWidth) {
 80             // 计算出实际宽度和目标宽度的比率
 81             final int widthRatio = Math.round((float) width / (float) reqWidth);
 82             inSampleSize = widthRatio;
 83         }
 84         return inSampleSize;
 85     }
 86 
 87     public static Bitmap decodeSampledBitmapFromResource(String pathName,
 88             int reqWidth) {
 89         // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小
 90         final BitmapFactory.Options options = new BitmapFactory.Options();
 91         options.inJustDecodeBounds = true;
 92         BitmapFactory.decodeFile(pathName, options);
 93         // 调用上面定义的方法计算inSampleSize值
 94         options.inSampleSize = calculateInSampleSize(options, reqWidth);
 95         // 使用获取到的inSampleSize值再次解析图片
 96         options.inJustDecodeBounds = false;
 97         return BitmapFactory.decodeFile(pathName, options);
 98     }
 99 
100 }

 

 

 

好了  大家照着自己做做就可以出来效果了,因为我已经把全部的源码贴出来了,如果你懒得连代码都不想敲。我觉得此文章对你没有什么作用。只有自己敲了就理解了

第一次写博客,感觉思路很乱,很多都没有讲明白。一个是时间不够,二是关键点都有注释,代码都贴上来了。共勉~~~~

转载于:https://www.cnblogs.com/anypwx/p/4711491.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现 图片的放大,缩小,左右屏幕滑动 。 直接贴代码吧。。 public class ViewPager extends ViewGroup { private static final String TAG = "ViewPager"; private static final boolean DEBUG = false; private static final boolean USE_CACHE = false; private static final int DEFAULT_OFFSCREEN_PAGES = 1; private static final int MAX_SETTLE_DURATION = 600; // ms private static final int PAGER_NEXT_MARGIN_DP = 40; static class ItemInfo { Object object; int position; boolean scrolling; } private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>() { @Override public int compare(ItemInfo lhs, ItemInfo rhs) { return lhs.position - rhs.position; } }; private static final Interpolator sInterpolator = new Interpolator() { public float getInterpolation(float t) { // _o(t) = t * t * ((tension + 1) * t + tension) // o(t) = _o(t - 1) + 1 t -= 1.0f; return t * t * t + 1.0f; } }; private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>(); private PagerAdapter mAdapter; private int mCurItem; // Index of currently displayed page. private int mRestoredCurItem = -1; private Parcelable mRestoredAdapterState = null; private ClassLoader mRestoredClassLoader = null; private Scroller mScroller; private PagerAdapter.DataSetObserver mObserver; private int mPageMargin; private Drawable mMarginDrawable; private int mChildWidthMeasureSpec; private int mChildHeightMeasureSpec; private boolean mInLayout; private boolean mScrollingCacheEnabled; private boolean mPopulatePending; private boolean mScrolling; private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES; private boolean mIsBeingDragged; private boolean mIsUnableToDrag; private int mTouchSlop; private float mInitialMotionX; /** * Position of the last motion event. */ private float mLastMotionX; private float mLastMotionY; /** * ID of the active pointer. This is used to retain consistency during * drags/flings if multiple pointers are used. */ private int mActivePointerId = INVALID_POINTER; /** * Sentinel value for no current active pointer. Used by * {@link #mActivePointerId}. */ private static final int INVALID_POINTER = -1; /** * Determines speed during touch scrolling */ private VelocityTracker mVelocityTracker; private int mMinimumVelocity; private int mMaximumVelocity; private float mBaseLineFlingVelocity; private float mFlingVelocityInfluence; private int mPagerNextMarginPixels; private boolean mFakeDragging; private long mFakeDragBeginTime; private EdgeEffectCompat mLeftEdge; private EdgeEffectCompat mRightEdge; private boolean mFirstLayout = true; private OnPageChangeListener mOnPageChangeListener; /** * Indicates that the pager is in an idle, settled state. The current page * is fully in view and no animation is in progress. */ public static final int SCROLL_STATE_IDLE = 0; /** * Indicates that the pager is currently being dragged by the user. */ public static final int SCROLL_STATE_DRAGGING = 1; /** * Indicates that the pager is in the process of settling to a final * position. */ public static final int SCROLL_STATE_SETTLING = 2; private int mScrollState = SCROLL_STATE_IDLE; /** * Callback interface for responding to changing state of the selected page. */ public interface OnPageChangeListener { /** * This method will be invoked when the current page is scrolled, either * as part of a programmatically initiated smooth scroll or a user * initiated touch scroll. * * @param position * Position index of the first page currently being * displayed. Page position+1 will be visible if * positionOffset is nonzero. * @param positionOffset * Value from [0, 1) indicating the offset from the page at * position. * @param positionOffsetPixels * Value in pixels indicating the offset from position. */ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels); /** * This method will be invoked when a new page becomes selected. * Animation is not necessarily complete. * * @param position * Position index of the new selected page. */ public void onPageSelected(int position, int prePosition); /** * Called when the scroll state changes. Useful for discovering when the * user begins dragging, when the pager is automatically settling to the * current page, or when it is fully stopped/idle. * * @param state * The new scroll state. * @see ViewPager#SCROLL_STATE_IDLE * @see ViewPager#SCROLL_STATE_DRAGGING * @see ViewPager#SCROLL_STATE_SETTLING */ public void onPageScrollStateChanged(int state); } /** * Simple implementation of the {@link OnPageChangeListener} interface with * stub implementations of each method. Extend this if you do not intend to * override every method of {@link OnPageChangeListener}. */ public static class SimpleOnPageChangeListener implements OnPageChangeListener { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { // This space for rent } @Override public void onPageSelected(int position, int prePosition) { // This space for rent } @Override public void onPageScrollStateChanged(int state) { // This space for rent } } public ViewPager(Context context) { super(context); initViewPager(); } public ViewPager(Context context, AttributeSet attrs) { super(context, attrs); initViewPager(); } void initViewPager() { setWillNotDraw(false); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); setFocusable(true); final Context context = getContext(); mScroller = new Scroller(context, sInterpolator); final ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = ViewConfigurationCompat .getScaledPagingTouchSlop(configuration); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mLeftEdge = new EdgeEffectCompat(context); mRightEdge = new EdgeEffectCompat(context); float density = context.getResources().getDisplayMetrics().density; mBaseLineFlingVelocity = 2500.0f * density; mFlingVelocityInfluence = 0.4f; final float scale = getResources().getDisplayMetrics().density; mPagerNextMarginPixels = (int) (PAGER_NEXT_MARGIN_DP * scale + 0.5f); } private void setScrollState(int newState) { if (mScrollState == newState) { return; } mScrollState = newState; if (mOnPageChangeListener != null) { mOnPageChangeListener.onPageScrollStateChanged(newState); } } public void setAdapter(PagerAdapter adapter) { if (mAdapter != null) { mAdapter.setDataSetObserver(null); mAdapter.startUpdate(this); for (int i = 0; i < mItems.size(); i++) { final ItemInfo ii = mItems.get(i); mAdapter.destroyItem(this, ii.position, ii.object); } mAdapter.finishUpdate(this); mItems.clear(); removeAllViews(); mCurItem = 0; scrollTo(0, 0); } mAdapter = adapter; if (mAdapter != null) { if (mObserver == null) { mObserver = new DataSetObserver(); } mAdapter.setDataSetObserver(mObserver); mPopulatePending = false; if (mRestoredCurItem >= 0) { mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader); setCurrentItemInternal(mRestoredCurItem, false, true); mRestoredCurItem = -1; mRestoredAdapterState = null; mRestoredClassLoader = null; } else { populate(); } } } public PagerAdapter getAdapter() { return mAdapter; } /** * Set the currently selected page. If the ViewPager has already been * through its first layout there will be a smooth animated transition * between the current item and the specified item. * * @param item * Item index to select */ public void setCurrentItem(int item) { mPopulatePending = false; setCurrentItemInternal(item, !mFirstLayout, false); } /** * Set the currently selected page. * * @param item * Item index to select * @param smoothScroll * True to smoothly scroll to the new item, false to transition * immediately */ public void setCurrentItem(int item, boolean smoothScroll) { mPopulatePending = false; setCurrentItemInternal(item, smoothScroll, false); } public int getCurrentItem() { return mCurItem; } void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) { setCurrentItemInternal(item, smoothScroll, always, 0); } void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { int preItem = mCurItem; if (mAdapter == null || mAdapter.getCount() <= 0) { setScrollingCacheEnabled(false); return; } if (!always && mCurItem == item && mItems.size() != 0) { setScrollingCacheEnabled(false); return; } if (item < 0) { item = 0; } else if (item >= mAdapter.getCount()) { item = mAdapter.getCount() - 1; } final int pageLimit = mOffscreenPageLimit; if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) { // We are doing a jump by more than one page. To avoid // glitches, we want to keep all current pages in the view // until the scroll ends. for (int i = 0; i < mItems.size(); i++) { mItems.get(i).scrolling = true; } } final boolean dispatchSelected = mCurItem != item; mCurItem = item; populate(); final int destX = (getWidth() + mPageMargin) * item; if (smoothScroll) { smoothScrollTo(destX, 0, velocity); if (dispatchSelected && mOnPageChangeListener != null) { mOnPageChangeListener.onPageSelected(item, preItem); } } else { if (dispatchSelected && mOnPageChangeListener != null) { mOnPageChangeListener.onPageSelected(item, preItem); } completeScroll(); scrollTo(destX, 0); } } public void setOnPageChangeListener(OnPageChangeListener listener) { mOnPageChangeListener = listener; } /** * Returns the number of pages that will be retained to either side of the * current page in the view hierarchy in an idle state. Defaults to 1. * * @return How many pages will be kept offscreen on either side * @see #setOffscreenPageLimit(int) */ public int getOffscreenPageLimit() { return mOffscreenPageLimit; } /** * Set the number of pages that should be retained to either side of the * current page in the view hierarchy in an idle state. Pages beyond this * limit will be recreated from the adapter when needed. * * <p> * This is offered as an optimization. If you know in advance the number of * pages you will need to support or have lazy-loading mechanisms in place * on your pages, tweaking this setting can have benefits in perceived * smoothness of paging animations and interaction. If you have a small * number of pages (3-4) that you can keep active all at once, less time * will be spent in layout for newly created view subtrees as the user pages * back and forth. * </p> * * <p> * You should keep this limit low, especially if your pages have complex * layouts. This setting defaults to 1. * </p> * * @param limit * How many pages will be kept offscreen in an idle state. */ public void setOffscreenPageLimit(int limit) { if (limit < DEFAULT_OFFSCREEN_PAGES) { Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " + DEFAULT_OFFSCREEN_PAGES); limit = DEFAULT_OFFSCREEN_PAGES; } if (limit != mOffscreenPageLimit) { mOffscreenPageLimit = limit; populate(); } } /** * Set the margin between pages. * * @param marginPixels * Distance between adjacent pages in pixels * @see #getPageMargin() * @see #setPageMarginDrawable(Drawable) * @see #setPageMarginDrawable(int) */ public void setPageMargin(int marginPixels) { final int oldMargin = mPageMargin; mPageMargin = marginPixels; final int width = getWidth(); recomputeScrollPosition(width, width, marginPixels, oldMargin); requestLayout(); } /** * Return the margin between pages. * * @return The size of the margin in pixels */ public int getPageMargin() { return mPageMargin; } /** * Set a drawable that will be used to fill the margin between pages. * * @param d * Drawable to display between pages */ public void setPageMarginDrawable(Drawable d) { mMarginDrawable = d; if (d != null) refreshDrawableState(); setWillNotDraw(d == null); invalidate(); } /** * Set a drawable that will be used to fill the margin between pages. * * @param resId * Resource ID of a drawable to display between pages */ public void setPageMarginDrawable(int resId) { setPageMarginDrawable(getContext().getResources().getDrawable(resId)); } @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || who == mMarginDrawable; } @Override protected void drawableStateChanged() { super.drawableStateChanged(); final Drawable d = mMarginDrawable; if (d != null && d.isStateful()) { d.setState(getDrawableState()); } } // We want the duration of the page snap animation to be influenced by the // distance that // the screen has to travel, however, we don't want this duration to be // effected in a // purely linear fashion. Instead, we use this method to moderate the effect // that the distance // of travel has on the overall snap duration. float distanceInfluenceForSnapDuration(float f) { f -= 0.5f; // center the values about 0. f *= 0.3f * Math.PI / 2.0f; return (float) Math.sin(f); } /** * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. * * @param x * the number of pixels to scroll by on the X axis * @param y * the number of pixels to scroll by on the Y axis */ void smoothScrollTo(int x, int y) { smoothScrollTo(x, y, 0); } /** * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. * * @param x * the number of pixels to scroll by on the X axis * @param y * the number of pixels to scroll by on the Y axis * @param velocity * the velocity associated with a fling, if applicable. (0 * otherwise) */ void smoothScrollTo(int x, int y, int velocity) { if (getChildCount() == 0) { // Nothing to do. setScrollingCacheEnabled(false); return; } int sx = getScrollX(); int sy = getScrollY(); int dx = x - sx; int dy = y - sy; if (dx == 0 && dy == 0) { completeScroll(); setScrollState(SCROLL_STATE_IDLE); return; } setScrollingCacheEnabled(true); mScrolling = true; setScrollState(SCROLL_STATE_SETTLING); final float pageDelta = (float) Math.abs(dx) / (getWidth() + mPageMargin); int duration = (int) (pageDelta * 100); velocity = Math.abs(velocity); if (velocity > 0) { duration += (duration / (velocity / mBaseLineFlingVelocity)) * mFlingVelocityInfluence; } else { duration += 100; } duration = Math.min(duration, MAX_SETTLE_DURATION); mScroller.startScroll(sx, sy, dx, dy, duration); invalidate(); } void addNewItem(int position, int index) { ItemInfo ii = new ItemInfo(); ii.position = position; ii.object = mAdapter.instantiateItem(this, position); if (index < 0) { mItems.add(ii); } else { mItems.add(index, ii); } } void dataSetChanged() { // This method only gets called if our observer is attached, so mAdapter // is non-null. boolean needPopulate = mItems.size() < 3 && mItems.size() < mAdapter.getCount(); int newCurrItem = -1; for (int i = 0; i < mItems.size(); i++) { final ItemInfo ii = mItems.get(i); final int newPos = mAdapter.getItemPosition(ii.object); if (newPos == PagerAdapter.POSITION_UNCHANGED) { continue; } if (newPos == PagerAdapter.POSITION_NONE) { mItems.remove(i); i--; mAdapter.destroyItem(this, ii.position, ii.object); needPopulate = true; if (mCurItem == ii.position) { // Keep the current item in the valid range newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1)); } continue; } if (ii.position != newPos) { if (ii.position == mCurItem) { // Our current item changed position. Follow it. newCurrItem = newPos; } ii.position = newPos; needPopulate = true; } } Collections.sort(mItems, COMPARATOR); if (newCurrItem >= 0) { // TODO This currently causes a jump. setCurrentItemInternal(newCurrItem, false, true); needPopulate = true; } if (needPopulate) { populate(); requestLayout(); } } void populate() { if (mAdapter == null) { return; } // Bail now if we are waiting to populate. This is to hold off // on creating views from the time the user releases their finger to // fling to a new position until we have finished the scroll to // that position, avoiding glitches from happening at that point. if (mPopulatePending) { if (DEBUG) Log.i(TAG, "populate is pending, skipping for now..."); return; } // Also, don't populate until we are attached to a window. This is to // avoid trying to populate before we have restored our view hierarchy // state and conflicting with what is restored. if (getWindowToken() == null) { return; } mAdapter.startUpdate(this); final int pageLimit = mOffscreenPageLimit; final int startPos = Math.max(0, mCurItem - pageLimit); final int N = mAdapter.getCount(); final int endPos = Math.min(N - 1, mCurItem + pageLimit); if (DEBUG) Log.v(TAG, "populating: startPos=" + startPos + " endPos=" + endPos); // Add and remove pages in the existing list. int lastPos = -1; for (int i = 0; i < mItems.size(); i++) { ItemInfo ii = mItems.get(i); if ((ii.position < startPos || ii.position > endPos) && !ii.scrolling) { if (DEBUG) Log.i(TAG, "removing: " + ii.position + " @ " + i); mItems.remove(i); i--; mAdapter.destroyItem(this, ii.position, ii.object); } else if (lastPos < endPos && ii.position > startPos) { // The next item is outside of our range, but we have a gap // between it and the last item where we want to have a page // shown. Fill in the gap. lastPos++; if (lastPos < startPos) { lastPos = startPos; } while (lastPos <= endPos && lastPos < ii.position) { if (DEBUG) Log.i(TAG, "inserting: " + lastPos + " @ " + i); addNewItem(lastPos, i); lastPos++; i++; } } lastPos = ii.position; } // Add any new pages we need at the end. lastPos = mItems.size() > 0 ? mItems.get(mItems.size() - 1).position : -1; if (lastPos < endPos) { lastPos++; lastPos = lastPos > startPos ? lastPos : startPos; while (lastPos <= endPos) { if (DEBUG) Log.i(TAG, "appending: " + lastPos); addNewItem(lastPos, -1); lastPos++; } } if (DEBUG) { Log.i(TAG, "Current page list:"); for (int i = 0; i < mItems.size(); i++) { Log.i(TAG, "#" + i + ": page " + mItems.get(i).position); } } ItemInfo curItem = null; for (int i = 0; i < mItems.size(); i++) { if (mItems.get(i).position == mCurItem) { curItem = mItems.get(i); break; } } mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null); mAdapter.finishUpdate(this); if (hasFocus()) { View currentFocused = findFocus(); ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null; if (ii == null || ii.position != mCurItem) { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); ii = infoForChild(child); if (ii != null && ii.position == mCurItem) { if (child.requestFocus(FOCUS_FORWARD)) { break; } } } } } } public static class SavedState extends BaseSavedState { int position; Parcelable adapterState; ClassLoader loader; public SavedState(Parcelable superState) { super(superState); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(position); out.writeParcelable(adapterState, flags); } @Override public String toString() { return "FragmentPager.SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " position=" + position + "}"; } public static final Parcelable.Creator<SavedState> CREATOR = ParcelableCompat .newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() { @Override public SavedState createFromParcel(Parcel in, ClassLoader loader) { return new SavedState(in, loader); } @Override public SavedState[] newArray(int size) { return new SavedState[size]; } }); SavedState(Parcel in, ClassLoader loader) { super(in); if (loader == null) { loader = getClass().getClassLoader(); } position = in.readInt(); adapterState = in.readParcelable(loader); this.loader = loader; } } @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); ss.position = mCurItem; if (mAdapter != null) { ss.adapterState = mAdapter.saveState(); } return ss; } @Override public void onRestoreInstanceState(Parcelable state) { if (!(state instanceof SavedState)) { super.onRestoreInstanceState(state); return; } SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); if (mAdapter != null) { mAdapter.restoreState(ss.adapterState, ss.loader); setCurrentItemInternal(ss.position, false, true); } else { mRestoredCurItem = ss.position; mRestoredAdapterState = ss.adapterState; mRestoredClassLoader = ss.loader; } } @Override public void addView(View child, int index, LayoutParams params) { if (mInLayout) { addViewInLayout(child, index, params); child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec); } else { super.addView(child, index, params); } if (USE_CACHE) { if (child.getVisibility() != GONE) { child.setDrawingCacheEnabled(mScrollingCacheEnabled); } else { child.setDrawingCacheEnabled(false); } } } ItemInfo infoForChild(View child) { for (int i = 0; i < mItems.size(); i++) { ItemInfo ii = mItems.get(i); if (mAdapter.isViewFromObject(child, ii.object)) { return ii; } } return null; } ItemInfo infoForAnyChild(View child) { ViewParent parent; while ((parent = child.getParent()) != this) { if (parent == null || !(parent instanceof View)) { return null; } child = (View) parent; } return infoForChild(child); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mFirstLayout = true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // For simple implementation, or internal size is always 0. // We depend on the container to specify the layout size of // our view. We can't really know what it is since we will be // adding and removing different arbitrary views and do not // want the layout to change as this happens. setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), getDefaultSize(0, heightMeasureSpec)); // Children are just made to fill our space. mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY); mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec( getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY); // Make sure we have created all fragments that we need to have shown. mInLayout = true; populate(); mInLayout = false; // Make sure all children have been properly measured. final int size = getChildCount(); for (int i = 0; i < size; ++i) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child + ": " + mChildWidthMeasureSpec); child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec); } } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // Make sure scroll position is set correctly. if (w != oldw) { recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin); } } private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) { final int widthWithMargin = width + margin; if (oldWidth > 0) { final int oldScrollPos = getScrollX(); final int oldwwm = oldWidth + oldMargin; final int oldScrollItem = oldScrollPos / oldwwm; final float scrollOffset = (float) (oldScrollPos % oldwwm) / oldwwm; final int scrollPos = (int) ((oldScrollItem + scrollOffset) * widthWithMargin); scrollTo(scrollPos, getScrollY()); if (!mScroller.isFinished()) { // We now return to your regularly scheduled scroll, already in // progress. final int newDuration = mScroller.getDuration() - mScroller.timePassed(); mScroller.startScroll(scrollPos, 0, mCurItem * widthWithMargin, 0, newDuration); } } else { int scrollPos = mCurItem * widthWithMargin; if (scrollPos != getScrollX()) { completeScroll(); scrollTo(scrollPos, getScrollY()); } } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { mInLayout = true; populate(); mInLayout = false; final int count = getChildCount(); final int width = r - l; for (int i = 0; i < count; i++) { View child = getChildAt(i); ItemInfo ii; if (child.getVisibility() != GONE && (ii = infoForChild(child)) != null) { int loff = (width + mPageMargin) * ii.position; int childLeft = getPaddingLeft() + loff; int childTop = getPaddingTop(); if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth() + "x" + child.getMeasuredHeight()); child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); } } mFirstLayout = false; } @Override public void computeScroll() { if (DEBUG) Log.i(TAG, "computeScroll: finished=" + mScroller.isFinished()); if (!mScroller.isFinished()) { if (mScroller.computeScrollOffset()) { if (DEBUG) Log.i(TAG, "computeScroll: still scrolling"); int oldX = getScrollX(); int oldY = getScrollY(); int x = mScroller.getCurrX(); int y = mScroller.getCurrY(); if (oldX != x || oldY != y) { scrollTo(x, y); } if (mOnPageChangeListener != null) { final int widthWithMargin = getWidth() + mPageMargin; final int position = x / widthWithMargin; final int offsetPixels = x % widthWithMargin; final float offset = (float) offsetPixels / widthWithMargin; mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels); } // Keep on drawing until the animation has finished. invalidate(); return; } } // Done with scroll, clean up state. completeScroll(); } private void completeScroll() { boolean needPopulate = mScrolling; if (needPopulate) { // Done with scroll, no longer want to cache view drawing. setScrollingCacheEnabled(false); mScroller.abortAnimation(); int oldX = getScrollX(); int oldY = getScrollY(); int x = mScroller.getCurrX(); int y = mScroller.getCurrY(); if (oldX != x || oldY != y) { scrollTo(x, y); } setScrollState(SCROLL_STATE_IDLE); } mPopulatePending = false; mScrolling = false; for (int i = 0; i < mItems.size(); i++) { ItemInfo ii = mItems.get(i); if (ii.scrolling) { needPopulate = true; ii.scrolling = false; } } if (needPopulate) { populate(); } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { /* * This method JUST determines whether we want to intercept the motion. * If we return true, onMotionEvent will be called and we do the actual * scrolling there. */ final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; // Always take care of the touch gesture being complete. if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { // Release the drag. if (DEBUG) Log.v(TAG, "Intercept done!"); mIsBeingDragged = false; mIsUnableToDrag = false; mActivePointerId = INVALID_POINTER; return false; } // Nothing more to do here if we have decided whether or not we // are dragging. if (action != MotionEvent.ACTION_DOWN) { if (mIsBeingDragged) { if (DEBUG) Log.v(TAG, "Intercept returning true!"); return true; } if (mIsUnableToDrag) { if (DEBUG) Log.v(TAG, "Intercept returning false!"); return false; } } switch (action) { case MotionEvent.ACTION_MOVE: { /* * mIsBeingDragged == false, otherwise the shortcut would have * caught it. Check whether the user has moved far enough from his * original down touch. */ /* * Locally do absolute value. mLastMotionY is set to the y value of * the down event. */ final int activePointerId = mActivePointerId; if (activePointerId == INVALID_POINTER) { // If we don't have a valid id, the touch down wasn't on // content. break; } final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); final float x = MotionEventCompat.getX(ev, pointerIndex); final float dx = x - mLastMotionX; final float xDiff = Math.abs(dx); final float y = MotionEventCompat.getY(ev, pointerIndex); final float yDiff = Math.abs(y - mLastMotionY); final int scrollX = getScrollX(); final boolean atEdge = (dx > 0 && scrollX == 0) || (dx < 0 && mAdapter != null && scrollX >= (mAdapter .getCount() - 1) * getWidth() - 1); if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); if (canScroll(this, false, (int) dx, (int) x, (int) y)) { // Nested view has scrollable area under this point. Let it be // handled there. mInitialMotionX = mLastMotionX = x; mLastMotionY = y; return false; } if (xDiff > mTouchSlop && xDiff > yDiff) { if (DEBUG) Log.v(TAG, "Starting drag!"); mIsBeingDragged = true; setScrollState(SCROLL_STATE_DRAGGING); mLastMotionX = x; setScrollingCacheEnabled(true); } else { if (yDiff > mTouchSlop) { // The finger has moved enough in the vertical // direction to be counted as a drag... abort // any attempt to drag horizontally, to work correctly // with children that have scrolling containers. if (DEBUG) Log.v(TAG, "Starting unable to drag!"); mIsUnableToDrag = true; } } break; } case MotionEvent.ACTION_DOWN: { /* * Remember location of down touch. ACTION_DOWN always refers to * pointer index 0. */ mLastMotionX = mInitialMotionX = ev.getX(); mLastMotionY = ev.getY(); mActivePointerId = MotionEventCompat.getPointerId(ev, 0); if (mScrollState == SCROLL_STATE_SETTLING) { // Let the user 'catch' the pager as it animates. mIsBeingDragged = true; mIsUnableToDrag = false; setScrollState(SCROLL_STATE_DRAGGING); } else { completeScroll(); mIsBeingDragged = false; mIsUnableToDrag = false; } if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY + " mIsBeingDragged=" + mIsBeingDragged + "mIsUnableToDrag=" + mIsUnableToDrag); break; } case MotionEventCompat.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; } /* * The only time we want to intercept motion events is if we are in the * drag mode. */ return mIsBeingDragged; } @Override public boolean onTouchEvent(MotionEvent ev) { if (mFakeDragging) { // A fake drag is in progress already, ignore this real one // but still eat the touch events. // (It is likely that the user is multi-touching the screen.) return true; } if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { // Don't handle edge touches immediately -- they may actually belong // to one of our // descendants. return false; } if (mAdapter == null || mAdapter.getCount() == 0) { // Nothing to present or scroll; nothing to touch. return false; } if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); final int action = ev.getAction(); boolean needsInvalidate = false; switch (action & MotionEventCompat.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { /* * If being flinged and user touches, stop the fling. isFinished * will be false if being flinged. */ completeScroll(); // Remember where the motion event started mLastMotionX = mInitialMotionX = ev.getX(); mActivePointerId = MotionEventCompat.getPointerId(ev, 0); break; } case MotionEvent.ACTION_MOVE: if (!mIsBeingDragged) { final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); final float x = MotionEventCompat.getX(ev, pointerIndex); final float xDiff = Math.abs(x - mLastMotionX); final float y = MotionEventCompat.getY(ev, pointerIndex); final float yDiff = Math.abs(y - mLastMotionY); if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); if (xDiff > mTouchSlop && xDiff > yDiff) { if (DEBUG) Log.v(TAG, "Starting drag!"); mIsBeingDragged = true; mLastMotionX = x; setScrollState(SCROLL_STATE_DRAGGING); setScrollingCacheEnabled(true); } } if (mIsBeingDragged) { // Scroll to follow the motion event final int activePointerIndex = MotionEventCompat .findPointerIndex(ev, mActivePointerId); final float x = MotionEventCompat.getX(ev, activePointerIndex); final float deltaX = mLastMotionX - x; mLastMotionX = x; float oldScrollX = getScrollX(); float scrollX = oldScrollX + deltaX; final int width = getWidth(); final int widthWithMargin = width + mPageMargin; final int lastItemIndex = mAdapter.getCount() - 1; final float leftBound = Math.max(0, (mCurItem - 1) * widthWithMargin); final float rightBound = Math.min(mCurItem + 1, lastItemIndex) * widthWithMargin; if (scrollX < leftBound) { if (leftBound == 0) { float over = -scrollX; needsInvalidate = mLeftEdge.onPull(over / width); } scrollX = leftBound; } else if (scrollX > rightBound) { if (rightBound == lastItemIndex * widthWithMargin) { float over = scrollX - rightBound; needsInvalidate = mRightEdge.onPull(over / width); } scrollX = rightBound; } // Don't lose the rounded component mLastMotionX += scrollX - (int) scrollX; scrollTo((int) scrollX, getScrollY()); if (mOnPageChangeListener != null) { final int position = (int) scrollX / widthWithMargin; final int positionOffsetPixels = (int) scrollX % widthWithMargin; final float positionOffset = (float) positionOffsetPixels / widthWithMargin; mOnPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels); } } break; case MotionEvent.ACTION_UP: if (mIsBeingDragged) { final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) VelocityTrackerCompat.getXVelocity( velocityTracker, mActivePointerId); mPopulatePending = true; final int widthWithMargin = getWidth() + mPageMargin; final int scrollX = getScrollX(); // 澧炲姞杈硅窛鍒ゆ柇 int currentPage = scrollX / widthWithMargin; int leftPageScorll = scrollX % widthWithMargin; int nextPage = currentPage; float chargeVelocity = initialVelocity; if (chargeVelocity > 0) { // 鍚戝彸 if (widthWithMargin - leftPageScorll > mPagerNextMarginPixels) { nextPage = currentPage; } else { nextPage = currentPage + 1; } } else { // 鍚戝乏 if (leftPageScorll > mPagerNextMarginPixels) { nextPage = currentPage + 1; } else { nextPage = currentPage; } } // nextPage = chargeVelocity > 0 ? currentPage // : currentPage + 1; // Log.d(TAG, "initialVelocity:" + initialVelocity // + ", currentPage: " + (scrollX / widthWithMargin) // + ", widthWithMargin: " + widthWithMargin // + ", scrollX: " + scrollX + ", chargeVelocity: " // + chargeVelocity + ", nextPage: " + nextPage); setCurrentItemInternal(nextPage, true, true, initialVelocity); mActivePointerId = INVALID_POINTER; endDrag(); needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); } break; case MotionEvent.ACTION_CANCEL: if (mIsBeingDragged) { setCurrentItemInternal(mCurItem, true, true); mActivePointerId = INVALID_POINTER; endDrag(); needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); } break; case MotionEventCompat.ACTION_POINTER_DOWN: { final int index = MotionEventCompat.getActionIndex(ev); final float x = MotionEventCompat.getX(ev, index); mLastMotionX = x; mActivePointerId = MotionEventCompat.getPointerId(ev, index); break; } case MotionEventCompat.ACTION_POINTER_UP: onSecondaryPointerUp(ev); mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId)); break; } if (needsInvalidate) { invalidate(); } return true; } @Override public void draw(Canvas canvas) { super.draw(canvas); boolean needsInvalidate = false; final int overScrollMode = ViewCompat.getOverScrollMode(this); if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && mAdapter != null && mAdapter.getCount() > 1)) { if (!mLeftEdge.isFinished()) { final int restoreCount = canvas.save(); final int height = getHeight() - getPaddingTop() - getPaddingBottom(); canvas.rotate(270); canvas.translate(-height + getPaddingTop(), 0); mLeftEdge.setSize(height, getWidth()); needsInvalidate |= mLeftEdge.draw(canvas); canvas.restoreToCount(restoreCount); } if (!mRightEdge.isFinished()) { final int restoreCount = canvas.save(); final int width = getWidth(); final int height = getHeight() - getPaddingTop() - getPaddingBottom(); final int itemCount = mAdapter != null ? mAdapter.getCount() : 1; canvas.rotate(90); canvas.translate(-getPaddingTop(), -itemCount * (width + mPageMargin) + mPageMargin); mRightEdge.setSize(height, width); needsInvalidate |= mRightEdge.draw(canvas); canvas.restoreToCount(restoreCount); } } else { mLeftEdge.finish(); mRightEdge.finish(); } if (needsInvalidate) { // Keep animating invalidate(); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // Draw the margin drawable if needed. if (mPageMargin > 0 && mMarginDrawable != null) { final int scrollX = getScrollX(); final int width = getWidth(); final int offset = scrollX % (width + mPageMargin); if (offset != 0) { // Pages fit completely when settled; we only need to draw when // in between final int left = scrollX - offset + width; mMarginDrawable.setBounds(left, 0, left + mPageMargin, getHeight()); mMarginDrawable.draw(canvas); } } } /** * Start a fake drag of the pager. * * <p> * A fake drag can be useful if you want to synchronize the motion of the * ViewPager with the touch scrolling of another view, while still letting * the ViewPager control the snapping motion and fling behavior. (e.g. * parallax-scrolling tabs.) Call {@link #fakeDragBy(float)} to simulate the * actual drag motion. Call {@link #endFakeDrag()} to complete the fake drag * and fling as necessary. * * <p> * During a fake drag the ViewPager will ignore all touch events. If a real * drag is already in progress, this method will return false. * * @return true if the fake drag began successfully, false if it could not * be started. * * @see #fakeDragBy(float) * @see #endFakeDrag() */ public boolean beginFakeDrag() { if (mIsBeingDragged) { return false; } mFakeDragging = true; setScrollState(SCROLL_STATE_DRAGGING); mInitialMotionX = mLastMotionX = 0; if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } else { mVelocityTracker.clear(); } final long time = SystemClock.uptimeMillis(); final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0); mVelocityTracker.addMovement(ev); ev.recycle(); mFakeDragBeginTime = time; return true; } /** * End a fake drag of the pager. * * @see #beginFakeDrag() * @see #fakeDragBy(float) */ public void endFakeDrag() { Log.d(TAG, "endFakeDrag"); if (!mFakeDragging) { throw new IllegalStateException( "No fake drag in progress. Call beginFakeDrag first."); } final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) VelocityTrackerCompat.getYVelocity( velocityTracker, mActivePointerId); mPopulatePending = true; Log.d(TAG, "initialVelocity: " + initialVelocity + ", Math.abs(mInitialMotionX - mLastMotionX): " + Math.abs(mInitialMotionX - mLastMotionX)); if ((Math.abs(initialVelocity) > mMinimumVelocity) || Math.abs(mInitialMotionX - mLastMotionX) >= (getWidth() / 3)) { if (mLastMotionX > mInitialMotionX) { setCurrentItemInternal(mCurItem - 1, true, true); } else { setCurrentItemInternal(mCurItem + 1, true, true); } } else { setCurrentItemInternal(mCurItem, true, true); } endDrag(); mFakeDragging = false; } /** * Fake drag by an offset in pixels. You must have called * {@link #beginFakeDrag()} first. * * @param xOffset * Offset in pixels to drag by. * @see #beginFakeDrag() * @see #endFakeDrag() */ public void fakeDragBy(float xOffset) { if (!mFakeDragging) { throw new IllegalStateException( "No fake drag in progress. Call beginFakeDrag first."); } mLastMotionX += xOffset; float scrollX = getScrollX() - xOffset; final int width = getWidth(); final int widthWithMargin = width + mPageMargin; final float leftBound = Math.max(0, (mCurItem - 1) * widthWithMargin); final float rightBound = Math .min(mCurItem + 1, mAdapter.getCount() - 1) * widthWithMargin; if (scrollX < leftBound) { scrollX = leftBound; } else if (scrollX > rightBound) { scrollX = rightBound; } // Don't lose the rounded component mLastMotionX += scrollX - (int) scrollX; scrollTo((int) scrollX, getScrollY()); if (mOnPageChangeListener != null) { final int position = (int) scrollX / widthWithMargin; final int positionOffsetPixels = (int) scrollX % widthWithMargin; final float positionOffset = (float) positionOffsetPixels / widthWithMargin; mOnPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels); } // Synthesize an event for the VelocityTracker. final long time = SystemClock.uptimeMillis(); final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, mLastMotionX, 0, 0); mVelocityTracker.addMovement(ev); ev.recycle(); } /** * Returns true if a fake drag is in progress. * * @return true if currently in a fake drag, false otherwise. * * @see #beginFakeDrag() * @see #fakeDragBy(float) * @see #endFakeDrag() */ public boolean isFakeDragging() { return mFakeDragging; } private void onSecondaryPointerUp(MotionEvent ev) { final int pointerIndex = MotionEventCompat.getActionIndex(ev); final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); if (pointerId == mActivePointerId) { // This was our active pointer going up. Choose a new // active pointer and adjust accordingly. final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex); mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); if (mVelocityTracker != null) { mVelocityTracker.clear(); } } } private void endDrag() { mIsBeingDragged = false; mIsUnableToDrag = false; if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } } private void setScrollingCacheEnabled(boolean enabled) { if (mScrollingCacheEnabled != enabled) { mScrollingCacheEnabled = enabled; if (USE_CACHE) { final int size = getChildCount(); for (int i = 0; i < size; ++i) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { child.setDrawingCacheEnabled(enabled); } } } } } /** * Tests scrollability within child views of v given a delta of dx. * * @param v * View to test for horizontal scrollability * @param checkV * Whether the view v passed should itself be checked for * scrollability (true), or just its children (false). * @param dx * Delta scrolled in pixels * @param x * X coordinate of the active touch point * @param y * Y coordinate of the active touch point * @return true if child views of v can be scrolled by delta of dx. */ protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { if (v instanceof ViewGroup) { final ViewGroup group = (ViewGroup) v; final int scrollX = v.getScrollX(); final int scrollY = v.getScrollY(); final int count = group.getChildCount(); // Count backwards - let topmost views consume scroll distance // first. for (int i = count - 1; i >= 0; i--) { // TODO: Add versioned support here for transformed views. // This will not work for transformed views in Honeycomb+ final View child = group.getChildAt(i); if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && canScroll(child, true, dx, x + scrollX - child.getLeft(), y + scrollY - child.getTop())) { return true; } } } return checkV && ViewCompat.canScrollHorizontally(v, -dx); } @Override public boolean dispatchKeyEvent(KeyEvent event) { // Let the focused view and/or our descendants get the key first return super.dispatchKeyEvent(event) || executeKeyEvent(event); } /** * You can call this function yourself to have the scroll view perform * scrolling from a key event, just as if the event had been dispatched to * it by the view hierarchy. * * @param event * The key event to execute. * @return Return true if the event was handled, else false. */ public boolean executeKeyEvent(KeyEvent event) { boolean handled = false; if (event.getAction() == KeyEvent.ACTION_DOWN) { switch (event.getKeyCode()) { case KeyEvent.KEYCODE_DPAD_LEFT: handled = arrowScroll(FOCUS_LEFT); break; case KeyEvent.KEYCODE_DPAD_RIGHT: handled = arrowScroll(FOCUS_RIGHT); break; case KeyEvent.KEYCODE_TAB: if (KeyEventCompat.hasNoModifiers(event)) { handled = arrowScroll(FOCUS_FORWARD); } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) { handled = arrowScroll(FOCUS_BACKWARD); } break; } } return handled; } public boolean arrowScroll(int direction) { View currentFocused = findFocus(); if (currentFocused == this) currentFocused = null; boolean handled = false; View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction); if (nextFocused != null && nextFocused != currentFocused) { if (direction == View.FOCUS_LEFT) { // If there is nothing to the left, or this is causing us to // jump to the right, then what we really want to do is page // left. if (currentFocused != null && nextFocused.getLeft() >= currentFocused.getLeft()) { handled = pageLeft(); } else { handled = nextFocused.requestFocus(); } } else if (direction == View.FOCUS_RIGHT) { // If there is nothing to the right, or this is causing us to // jump to the left, then what we really want to do is page // right. if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) { handled = pageRight(); } else { handled = nextFocused.requestFocus(); } } } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) { // Trying to move left and nothing there; try to page. handled = pageLeft(); } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) { // Trying to move right and nothing there; try to page. handled = pageRight(); } if (handled) { playSoundEffect(SoundEffectConstants .getContantForFocusDirection(direction)); } return handled; } boolean pageLeft() { if (mCurItem > 0) { setCurrentItem(mCurItem - 1, true); return true; } return false; } boolean pageRight() { if (mAdapter != null && mCurItem < (mAdapter.getCount() - 1)) { setCurrentItem(mCurItem + 1, true); return true; } return false; } /** * We only want the current page that is being shown to be focusable. */ @Override public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { final int focusableCount = views.size(); final int descendantFocusability = getDescendantFocusability(); if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); if (child.getVisibility() == VISIBLE) { ItemInfo ii = infoForChild(child); if (ii != null && ii.position == mCurItem) { child.addFocusables(views, direction, focusableMode); } } } } // we add ourselves (if focusable) in all cases except for when we are // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. // this is // to avoid the focus search finding layouts when a more precise search // among the focusable children would be more interesting. if (descendantFocusability != FOCUS_AFTER_DESCENDANTS || // No focusable descendants (focusableCount == views.size())) { // Note that we can't call the superclass here, because it will // add all views in. So we need to do the same thing View does. if (!isFocusable()) { return; } if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE && isInTouchMode() && !isFocusableInTouchMode()) { return; } if (views != null) { views.add(this); } } } /** * We only want the current page that is being shown to be touchable. */ @Override public void addTouchables(ArrayList<View> views) { // Note that we don't call super.addTouchables(), which means that // we don't call View.addTouchables(). This is okay because a ViewPager // is itself not touchable. for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); if (child.getVisibility() == VISIBLE) { ItemInfo ii = infoForChild(child); if (ii != null && ii.position == mCurItem) { child.addTouchables(views); } } } } /** * We only want the current page that is being shown to be focusable. */ @Override protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { int index; int increment; int end; int count = getChildCount(); if ((direction & FOCUS_FORWARD) != 0) { index = 0; increment = 1; end = count; } else { index = count - 1; increment = -1; end = -1; } for (int i = index; i != end; i += increment) { View child = getChildAt(i); if (child.getVisibility() == VISIBLE) { ItemInfo ii = infoForChild(child); if (ii != null && ii.position == mCurItem) { if (child.requestFocus(direction, previouslyFocusedRect)) { return true; } } } } return false; } @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { // ViewPagers should only report accessibility info for the current // page, // otherwise things get very confusing. // TODO: Should this note something about the paging container? final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (child.getVisibility() == VISIBLE) { final ItemInfo ii = infoForChild(child); if (ii != null && ii.position == mCurItem && child.dispatchPopulateAccessibilityEvent(event)) { return true; } } } return false; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值