一、代码
1.Applation
package com.tianyu.demo.image;
import android.app.Application;
import com.loveplusplus.demo.image.R;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder()
.showImageForEmptyUri(R.drawable.empty_photo)
// 为空时显示
.showImageOnFail(R.drawable.empty_photo)// 错误时显示
.cacheInMemory(true)// 缓存
.cacheOnDisc(true).build();
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(
getApplicationContext())
.defaultDisplayImageOptions(defaultOptions)
.discCacheSize(50 * 1024 * 1024)// 缓存大小
.discCacheFileCount(100)// 缓存一百张图片
.writeDebugLogs().build();
ImageLoader.getInstance().init(config);
}
}
2.MainActivity
package com.tianyu.demo.image;
import android.app.ListActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import com.google.gson.Gson;
import com.loveplusplus.demo.image.R;
public class MainActivity extends ListActivity {
public static final String TAG = "MainActivity";
// 适配器
private MyListAdapter mAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyTask().execute();
}
/**
* 异步
*
* @author Administrator
*
*/
class MyTask extends AsyncTask<Void, Void, MyMessage> {
@Override
protected MyMessage doInBackground(Void... params) {
String json = "{\"code\":200,\"msg\":\"ok\",list:["
+ "{\"id\":110,\"avator\":\"http://img0.bdstatic.com/img/image/shouye/leimu/mingxing.jpg\",\"name\":\"张三\",\"content\":\"大家好\",\"urls\":[]},"
+ "{\"id\":111,\"avator\":\"http://img0.bdstatic.com/img/image/shouye/leimu/mingxing2.jpg\",\"name\":\"李四\",\"content\":\"大家好\",\"urls\":[\"http://d.hiphotos.baidu.com/album/w%3D2048/sign=14b0934b78310a55c424d9f4837d42a9/a8014c086e061d95e9fd3e807af40ad163d9cacb.jpg\"]},"
+ "{\"id\":112,\"avator\":\"http://img0.bdstatic.com/img/image/shouye/leimu/mingxing1.jpg\",\"name\":\"王五\",\"content\":\"大家好\",\"urls\":[\"http://g.hiphotos.bdimg.com/album/s%3D680%3Bq%3D90/sign=ccd33b46d53f8794d7ff4b26e2207fc9/0d338744ebf81a4c0f993437d62a6059242da6a1.jpg\",\"http://c.hiphotos.bdimg.com/album/s%3D900%3Bq%3D90/sign=b8658f17f3d3572c62e290dcba28121a/5fdf8db1cb134954bb97309a574e9258d0094a47.jpg\"]},"
+ "{\"id\":113,\"avator\":\"http://img0.bdstatic.com/img/image/shouye/leimu/mingxing6.jpg\",\"name\":\"赵六\",\"content\":\"大家好\",\"urls\":[\"http://f.hiphotos.bdimg.com/album/s%3D680%3Bq%3D90/sign=6b62f61bac6eddc422e7b7f309e0c7c0/6159252dd42a2834510deef55ab5c9ea14cebfa1.jpg\",\"http://g.hiphotos.bdimg.com/album/s%3D680%3Bq%3D90/sign=e58fb67bc8ea15ce45eee301863b4bce/a5c27d1ed21b0ef4fd6140a0dcc451da80cb3e47.jpg\",\"http://c.hiphotos.bdimg.com/album/s%3D680%3Bq%3D90/sign=cdab1512d000baa1be2c44b3772bc82f/91529822720e0cf3855c96050b46f21fbf09aaa1.jpg\"]}]}";
Gson gson = new Gson();
MyMessage msg = gson.fromJson(json, MyMessage.class);
return msg;
}
@Override
protected void onPostExecute(MyMessage result) {
mAdapter = new MyListAdapter(MainActivity.this, result.list);
setListAdapter(mAdapter);
}
}
}
3.MyBean
package com.tianyu.demo.image;
public class MyBean {
// ID
public int id;
// 头像
public String avator;
// 名称
public String name;
// 内容
public String content;
// 时间
public String time;
// url
public String[] urls;
}
4.
package com.tianyu.demo.image;
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
/**
* Hacky fix for Issue #4 and
* http://code.google.com/p/android/issues/detail?id=18990
*
* ScaleGestureDetector seems to mess up the touch events, which means that
* ViewGroups which make use of onInterceptTouchEvent throw a lot of
* IllegalArgumentException: pointerIndex out of range.
*
* There's not much I can do in my code for now, but we can mask the result by
* just catching the problem and ignoring it.
*
* @author Chris Banes
*/
public class HackyViewPager extends ViewPager {
private static final String TAG = "HackyViewPager";
public HackyViewPager(Context context) {
super(context);
}
public HackyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* 监听手势触摸事件
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
try {
return super.onInterceptTouchEvent(ev);
} catch (IllegalArgumentException e) {
// 不理会
Log.e(TAG, "hacky viewpager error1");
return false;
} catch (ArrayIndexOutOfBoundsException e) {
// 不理会
Log.e(TAG, "hacky viewpager error2");
return false;
}
}
}
5.
package com.tianyu.demo.image;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;
import com.loveplusplus.demo.image.R;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.assist.SimpleImageLoadingListener;
import com.tianyu.photoview.PhotoViewAttacher;
import com.tianyu.photoview.PhotoViewAttacher.OnPhotoTapListener;
/**
* 图片侧滑的Fragment
*
* @author Administrator
*
*/
public class ImageDetailFragment extends Fragment {
// 图片url
private String mImageUrl;
// imageview
private ImageView mImageView;
// 进度条
private ProgressBar progressBar;
// 图片
private PhotoViewAttacher mAttacher;
public static ImageDetailFragment newInstance(String imageUrl) {
// 初始化
final ImageDetailFragment f = new ImageDetailFragment();
// 传值
final Bundle args = new Bundle();
args.putString("url", imageUrl);
f.setArguments(args);
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mImageUrl = getArguments() != null ? getArguments().getString("url")
: null;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.image_detail_fragment,
container, false);
mImageView = (ImageView) v.findViewById(R.id.image);
mAttacher = new PhotoViewAttacher(mImageView);
mAttacher.setOnPhotoTapListener(new OnPhotoTapListener() {
@Override
public void onPhotoTap(View arg0, float arg1, float arg2) {
getActivity().finish();
}
});
progressBar = (ProgressBar) v.findViewById(R.id.loading);
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// 加载图片
ImageLoader.getInstance().displayImage(mImageUrl, mImageView,
new SimpleImageLoadingListener() {
@Override
public void onLoadingStarted(String imageUri, View view) {
progressBar.setVisibility(View.VISIBLE);
}
/**
* 加载失败
*/
@Override
public void onLoadingFailed(String imageUri, View view,
FailReason failReason) {
String message = null;
switch (failReason.getType()) {
case IO_ERROR:
message = "下载错误";
break;
case DECODING_ERROR:
message = "图片无法显示";
break;
case NETWORK_DENIED:
message = "网络有问题,无法下载";
break;
case OUT_OF_MEMORY:
message = "图片太大无法显示";
break;
case UNKNOWN:
message = "未知的错误";
break;
}
Toast.makeText(getActivity(), message,
Toast.LENGTH_SHORT).show();
progressBar.setVisibility(View.GONE);
}
/**
* 加载完成
*/
@Override
public void onLoadingComplete(String imageUri, View view,
Bitmap loadedImage) {
progressBar.setVisibility(View.GONE);
mAttacher.update();
}
});
}
}
6.
package com.tianyu.demo.image;
import com.loveplusplus.demo.image.R;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.widget.TextView;
/**
* 图片详细信息
*
* @author Administrator
*
*/
public class ImagePagerActivity extends FragmentActivity {
// 位置
private static final String STATE_POSITION = "STATE_POSITION";
// 图片下标
public static final String EXTRA_IMAGE_INDEX = "image_index";
// 图片url
public static final String EXTRA_IMAGE_URLS = "image_urls";
private HackyViewPager mPager;
// 图片位置
private int pagerPosition;
// 指示器
private TextView indicator;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.image_detail_pager);
pagerPosition = getIntent().getIntExtra(EXTRA_IMAGE_INDEX, 0);
String[] urls = getIntent().getStringArrayExtra(EXTRA_IMAGE_URLS);
mPager = (HackyViewPager) findViewById(R.id.pager);
// 适配器
ImagePagerAdapter mAdapter = new ImagePagerAdapter(
getSupportFragmentManager(), urls);
mPager.setAdapter(mAdapter);
indicator = (TextView) findViewById(R.id.indicator);
// 设置指示器
CharSequence text = getString(R.string.viewpager_indicator, 1, mPager
.getAdapter().getCount());
indicator.setText(text);
// 更新下标(页面改变监听器)
mPager.setOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageScrollStateChanged(int arg0) {
}
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
/**
* 选择页面
*/
@Override
public void onPageSelected(int arg0) {
CharSequence text = getString(R.string.viewpager_indicator,
arg0 + 1, mPager.getAdapter().getCount());
indicator.setText(text);
}
});
if (savedInstanceState != null) {
pagerPosition = savedInstanceState.getInt(STATE_POSITION);
}
// 设置实时位置
mPager.setCurrentItem(pagerPosition);
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(STATE_POSITION, mPager.getCurrentItem());
}
/**
* 适配器
*
* @author Administrator
*
*/
private class ImagePagerAdapter extends FragmentStatePagerAdapter {
public String[] fileList;
public ImagePagerAdapter(FragmentManager fm, String[] fileList) {
super(fm);
this.fileList = fileList;
}
@Override
public int getCount() {
return fileList == null ? 0 : fileList.length;
}
@Override
public Fragment getItem(int position) {
String url = fileList[position];
return ImageDetailFragment.newInstance(url);
}
}
}
7.适配器
package com.tianyu.demo.image;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import com.loveplusplus.demo.image.R;
import com.nostra13.universalimageloader.core.ImageLoader;
/**
* 图片适配器
*
* @author Administrator
*
*/
public class MyGridAdapter extends BaseAdapter {
// 文件
private final String[] files;
private final LayoutInflater mLayoutInflater;
public MyGridAdapter(String[] files, Context context) {
this.files = files;
mLayoutInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return files == null ? 0 : files.length;
}
@Override
public String getItem(int position) {
return files[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
MyGridViewHolder viewHolder;
if (convertView == null) {
viewHolder = new MyGridViewHolder();
convertView = mLayoutInflater.inflate(R.layout.gridview_item,
parent, false);
viewHolder.imageView = (ImageView) convertView
.findViewById(R.id.album_image);
convertView.setTag(viewHolder);
} else {
viewHolder = (MyGridViewHolder) convertView.getTag();
}
String url = getItem(position);
ImageLoader.getInstance().displayImage(url, viewHolder.imageView);
return convertView;
}
private static class MyGridViewHolder {
ImageView imageView;
}
}
8.
package com.tianyu.demo.image;
import java.util.ArrayList;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.loveplusplus.demo.image.R;
import com.nostra13.universalimageloader.core.ImageLoader;
/**
* 总适配器
*
* @author Administrator
*
*/
public class MyListAdapter extends BaseAdapter {
private final ArrayList<MyBean> mList;
private final LayoutInflater mInflater;
private final Context mContext;
public MyListAdapter(Context context, ArrayList<MyBean> list) {
mInflater = LayoutInflater.from(context);
mContext = context;
this.mList = list;
}
@Override
public int getCount() {
return mList == null ? 0 : mList.size();
}
@Override
public MyBean getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return getItem(position).id;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.list_item, null);
holder.avator = (ImageView) convertView.findViewById(R.id.avator);
holder.name = (TextView) convertView.findViewById(R.id.name);
holder.content = (TextView) convertView.findViewById(R.id.content);
holder.gridView = (NoScrollGridView) convertView
.findViewById(R.id.gridView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
final MyBean bean = getItem(position);
ImageLoader.getInstance().displayImage(bean.avator, holder.avator);
holder.name.setText(bean.name);
holder.content.setText(bean.content);
if (bean.urls != null && bean.urls.length > 0) {
holder.gridView.setVisibility(View.VISIBLE);
// 设置适配器
holder.gridView.setAdapter(new MyGridAdapter(bean.urls, mContext));
// 设置点击事件
holder.gridView
.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent,
View view, int position, long id) {
imageBrower(position, bean.urls);
}
});
} else {
holder.gridView.setVisibility(View.GONE);
}
return convertView;
}
/**
* 显示图片
*
* @param position
* @param urls
*/
private void imageBrower(int position, String[] urls) {
Intent intent = new Intent(mContext, ImagePagerActivity.class);
// 图片url,为了演示这里使用常量,一般从数据库中或网络中获取
intent.putExtra(ImagePagerActivity.EXTRA_IMAGE_URLS, urls);
intent.putExtra(ImagePagerActivity.EXTRA_IMAGE_INDEX, position);
mContext.startActivity(intent);
}
private static class ViewHolder {
// 名字
public TextView name;
// 头像
public ImageView avator;
// 内容
TextView content;
// gridView显示图片
NoScrollGridView gridView;
}
}
9.
package com.tianyu.demo.image;
import java.util.ArrayList;
public class MyMessage {
public int code;
public String msg;
public ArrayList<MyBean> list;
@Override
public String toString() {
return "MyMessage [code=" + code + ", msg=" + msg + ", list=" + list
+ "]";
}
}
10
package com.tianyu.demo.image;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.GridView;
/**
* 自定义gridview
*
* @author Administrator
*
*/
public class NoScrollGridView extends GridView {
public NoScrollGridView(Context context) {
super(context);
}
public NoScrollGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
11、PhotoView
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.tianyu.photoview;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.view.MotionEvent;
import android.view.View;
public class Compat {
private static final int SIXTY_FPS_INTERVAL = 1000 / 60;
public static void postOnAnimation(View view, Runnable runnable) {
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
postOnAnimationJellyBean(view, runnable);
} else {
view.postDelayed(runnable, SIXTY_FPS_INTERVAL);
}
}
@TargetApi(16)
private static void postOnAnimationJellyBean(View view, Runnable runnable) {
view.postOnAnimation(runnable);
}
public static int getPointerIndex(int action) {
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB)
return getPointerIndexHoneyComb(action);
else
return getPointerIndexEclair(action);
}
@SuppressWarnings("deprecation")
@TargetApi(Build.VERSION_CODES.ECLAIR)
private static int getPointerIndexEclair(int action) {
return (action & MotionEvent.ACTION_POINTER_ID_MASK) >> MotionEvent.ACTION_POINTER_ID_SHIFT;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static int getPointerIndexHoneyComb(int action) {
return (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
}
}
12、
package com.tianyu.photoview;
import android.graphics.RectF;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.widget.ImageView;
/**
*
* 默认的双击事件<br>
*
*
* Provided default implementation of GestureDetector.OnDoubleTapListener, to be
* overriden with custom behavior, if needed
* <p>
*
* </p>
* To be used via
* {@link com.tianyu.photoview.PhotoViewAttacher#setOnDoubleTapListener(android.view.GestureDetector.OnDoubleTapListener)}
*/
public class DefaultOnDoubleTapListener implements
GestureDetector.OnDoubleTapListener {
private PhotoViewAttacher photoViewAttacher;
/**
* Default constructor
*
* @param photoViewAttacher
* PhotoViewAttacher to bind to
*/
public DefaultOnDoubleTapListener(PhotoViewAttacher photoViewAttacher) {
setPhotoViewAttacher(photoViewAttacher);
}
/**
* Allows to change PhotoViewAttacher within range of single instance
*
* @param newPhotoViewAttacher
* PhotoViewAttacher to bind to
*/
public void setPhotoViewAttacher(PhotoViewAttacher newPhotoViewAttacher) {
this.photoViewAttacher = newPhotoViewAttacher;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (this.photoViewAttacher == null)
return false;
ImageView imageView = photoViewAttacher.getImageView();
if (null != photoViewAttacher.getOnPhotoTapListener()) {
final RectF displayRect = photoViewAttacher.getDisplayRect();
if (null != displayRect) {
final float x = e.getX(), y = e.getY();
// Check to see if the user tapped on the photo
if (displayRect.contains(x, y)) {
float xResult = (x - displayRect.left)
/ displayRect.width();
float yResult = (y - displayRect.top)
/ displayRect.height();
photoViewAttacher.getOnPhotoTapListener().onPhotoTap(
imageView, xResult, yResult);
return true;
}
}
}
if (null != photoViewAttacher.getOnViewTapListener()) {
photoViewAttacher.getOnViewTapListener().onViewTap(imageView,
e.getX(), e.getY());
}
return false;
}
@Override
public boolean onDoubleTap(MotionEvent ev) {
if (photoViewAttacher == null)
return false;
try {
float scale = photoViewAttacher.getScale();
float x = ev.getX();
float y = ev.getY();
if (scale < photoViewAttacher.getMediumScale()) {
photoViewAttacher.setScale(photoViewAttacher.getMediumScale(),
x, y, true);
} else if (scale >= photoViewAttacher.getMediumScale()
&& scale < photoViewAttacher.getMaximumScale()) {
photoViewAttacher.setScale(photoViewAttacher.getMaximumScale(),
x, y, true);
} else {
photoViewAttacher.setScale(photoViewAttacher.getMinimumScale(),
x, y, true);
}
} catch (ArrayIndexOutOfBoundsException e) {
// Can sometimes happen when getX() and getY() is called
}
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
// Wait for the confirmed onDoubleTap() instead
return false;
}
}
13、
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.tianyu.photoview;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.view.View;
import android.widget.ImageView;
public interface IPhotoView {
// 默认最大拉伸
public static final float DEFAULT_MAX_SCALE = 3.0f;
// 普通拉伸
public static final float DEFAULT_MID_SCALE = 1.75f;
// 最小拉伸
public static final float DEFAULT_MIN_SCALE = 1.0f;
// 默认对焦位置
public static final int DEFAULT_ZOOM_DURATION = 200;
/**
* Returns true if the PhotoView is set to allow zooming of Photos.
*
* @return true if the PhotoView allows zooming.
*/
boolean canZoom();
/**
* Gets the Display Rectangle of the currently displayed Drawable. The
* Rectangle is relative to this View and includes all scaling and
* translations.
*
* @return - RectF of Displayed Drawable
*/
RectF getDisplayRect();
/**
* Sets the Display Matrix of the currently displayed Drawable. The
* Rectangle is considered relative to this View and includes all scaling
* and translations.
*
* @return - true if rectangle was applied successfully
*/
boolean setDisplayMatrix(Matrix finalMatrix);
/**
* Gets the Display Matrix of the currently displayed Drawable. The
* Rectangle is considered relative to this View and includes all scaling
* and translations.
*
* @return - true if rectangle was applied successfully
*/
Matrix getDisplayMatrix();
/**
* Use {@link #getMinimumScale()} instead, this will be removed in future
* release
*
* @return The current minimum scale level. What this value represents
* depends on the current {@link android.widget.ImageView.ScaleType}
* .
*/
@Deprecated
float getMinScale();
/**
* @return The current minimum scale level. What this value represents
* depends on the current {@link android.widget.ImageView.ScaleType}
* .
*/
float getMinimumScale();
/**
* Use {@link #getMediumScale()} instead, this will be removed in future
* release
*
* @return The current middle scale level. What this value represents
* depends on the current {@link android.widget.ImageView.ScaleType}
* .
*/
@Deprecated
float getMidScale();
/**
* @return The current medium scale level. What this value represents
* depends on the current {@link android.widget.ImageView.ScaleType}
* .
*/
float getMediumScale();
/**
* Use {@link #getMaximumScale()} instead, this will be removed in future
* release
*
* @return The current maximum scale level. What this value represents
* depends on the current {@link android.widget.ImageView.ScaleType}
* .
*/
@Deprecated
float getMaxScale();
/**
* @return The current maximum scale level. What this value represents
* depends on the current {@link android.widget.ImageView.ScaleType}
* .
*/
float getMaximumScale();
/**
* Returns the current scale value
*
* @return float - current scale value
*/
float getScale();
/**
* Return the current scale type in use by the ImageView.
*/
ImageView.ScaleType getScaleType();
/**
* Whether to allow the ImageView's parent to intercept the touch event when
* the photo is scroll to it's horizontal edge.
*/
void setAllowParentInterceptOnEdge(boolean allow);
/**
* Use {@link #setMinimumScale(float minimumScale)} instead, this will be
* removed in future release
* <p/>
* Sets the minimum scale level. What this value represents depends on the
* current {@link android.widget.ImageView.ScaleType}.
*/
@Deprecated
void setMinScale(float minScale);
/**
* Sets the minimum scale level. What this value represents depends on the
* current {@link android.widget.ImageView.ScaleType}.
*/
void setMinimumScale(float minimumScale);
/**
* Use {@link #setMediumScale(float mediumScale)} instead, this will be
* removed in future release
* <p/>
* Sets the middle scale level. What this value represents depends on the
* current {@link android.widget.ImageView.ScaleType}.
*/
@Deprecated
void setMidScale(float midScale);
/*
* Sets the medium scale level. What this value represents depends on the
* current {@link android.widget.ImageView.ScaleType}.
*/
void setMediumScale(float mediumScale);
/**
* Use {@link #setMaximumScale(float maximumScale)} instead, this will be
* removed in future release
* <p/>
* Sets the maximum scale level. What this value represents depends on the
* current {@link android.widget.ImageView.ScaleType}.
*/
@Deprecated
void setMaxScale(float maxScale);
/**
* Sets the maximum scale level. What this value represents depends on the
* current {@link android.widget.ImageView.ScaleType}.
*/
void setMaximumScale(float maximumScale);
/**
* Register a callback to be invoked when the Photo displayed by this view
* is long-pressed.
*
* @param listener
* - Listener to be registered.
*/
void setOnLongClickListener(View.OnLongClickListener listener);
/**
* Register a callback to be invoked when the Matrix has changed for this
* View. An example would be the user panning or scaling the Photo.
*
* @param listener
* - Listener to be registered.
*/
void setOnMatrixChangeListener(
PhotoViewAttacher.OnMatrixChangedListener listener);
/**
* Register a callback to be invoked when the Photo displayed by this View
* is tapped with a single tap.
*
* @param listener
* - Listener to be registered.
*/
void setOnPhotoTapListener(PhotoViewAttacher.OnPhotoTapListener listener);
/**
* Returns a listener to be invoked when the Photo displayed by this View is
* tapped with a single tap.
*
* @return PhotoViewAttacher.OnPhotoTapListener currently set, may be null
*/
PhotoViewAttacher.OnPhotoTapListener getOnPhotoTapListener();
/**
* Register a callback to be invoked when the View is tapped with a single
* tap.
*
* @param listener
* - Listener to be registered.
*/
void setOnViewTapListener(PhotoViewAttacher.OnViewTapListener listener);
/**
* Returns a callback listener to be invoked when the View is tapped with a
* single tap.
*
* @return PhotoViewAttacher.OnViewTapListener currently set, may be null
*/
PhotoViewAttacher.OnViewTapListener getOnViewTapListener();
/**
* Changes the current scale to the specified value.
*
* @param scale
* - Value to scale to
*/
void setScale(float scale);
/**
* Changes the current scale to the specified value.
*
* @param scale
* - Value to scale to
* @param animate
* - Whether to animate the scale
*/
void setScale(float scale, boolean animate);
/**
* Changes the current scale to the specified value, around the given focal
* point.
*
* @param scale
* - Value to scale to
* @param focalX
* - X Focus Point
* @param focalY
* - Y Focus Point
* @param animate
* - Whether to animate the scale
*/
void setScale(float scale, float focalX, float focalY, boolean animate);
/**
* Controls how the image should be resized or moved to match the size of
* the ImageView. Any scaling or panning will happen within the confines of
* this {@link android.widget.ImageView.ScaleType}.
*
* @param scaleType
* - The desired scaling mode.
*/
void setScaleType(ImageView.ScaleType scaleType);
/**
* Allows you to enable/disable the zoom functionality on the ImageView.
* When disable the ImageView reverts to using the FIT_CENTER matrix.
*
* @param zoomable
* - Whether the zoom functionality is enabled.
*/
void setZoomable(boolean zoomable);
/**
* Enables rotation via PhotoView internal functions. Name is chosen so it
* won't collide with View.setRotation(float) in API since 11
*
* @param rotationDegree
* - Degree to rotate PhotoView by, should be in range 0 to 360
*/
void setPhotoViewRotation(float rotationDegree);
/**
* Extracts currently visible area to Bitmap object, if there is no image
* loaded yet or the ImageView is already destroyed, returns {@code null}
*
* @return currently visible area as bitmap or null
*/
Bitmap getVisibleRectangleBitmap();
/**
* Allows to change zoom transition speed, default value is 200
* (PhotoViewAttacher.DEFAULT_ZOOM_DURATION). Will default to 200 if
* provided negative value
*
* @param milliseconds
* duration of zoom interpolation
*/
void setZoomTransitionDuration(int milliseconds);
}
15、
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.tianyu.photoview;
import com.tianyu.photoview.PhotoViewAttacher.OnMatrixChangedListener;
import com.tianyu.photoview.PhotoViewAttacher.OnPhotoTapListener;
import com.tianyu.photoview.PhotoViewAttacher.OnViewTapListener;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
* 图片
*
* @author Administrator
*
*/
public class PhotoView extends ImageView implements IPhotoView {
private final PhotoViewAttacher mAttacher;
private ScaleType mPendingScaleType;
public PhotoView(Context context) {
this(context, null);
}
public PhotoView(Context context, AttributeSet attr) {
this(context, attr, 0);
}
public PhotoView(Context context, AttributeSet attr, int defStyle) {
super(context, attr, defStyle);
super.setScaleType(ScaleType.MATRIX);
mAttacher = new PhotoViewAttacher(this);
if (null != mPendingScaleType) {
setScaleType(mPendingScaleType);
mPendingScaleType = null;
}
}
@Override
public void setPhotoViewRotation(float rotationDegree) {
mAttacher.setPhotoViewRotation(rotationDegree);
}
@Override
public boolean canZoom() {
return mAttacher.canZoom();
}
@Override
public RectF getDisplayRect() {
return mAttacher.getDisplayRect();
}
@Override
public Matrix getDisplayMatrix() {
return mAttacher.getDrawMatrix();
}
@Override
public boolean setDisplayMatrix(Matrix finalRectangle) {
return mAttacher.setDisplayMatrix(finalRectangle);
}
@Override
@Deprecated
public float getMinScale() {
return getMinimumScale();
}
@Override
public float getMinimumScale() {
return mAttacher.getMinimumScale();
}
@Override
@Deprecated
public float getMidScale() {
return getMediumScale();
}
@Override
public float getMediumScale() {
return mAttacher.getMediumScale();
}
@Override
@Deprecated
public float getMaxScale() {
return getMaximumScale();
}
@Override
public float getMaximumScale() {
return mAttacher.getMaximumScale();
}
@Override
public float getScale() {
return mAttacher.getScale();
}
@Override
public ScaleType getScaleType() {
return mAttacher.getScaleType();
}
@Override
public void setAllowParentInterceptOnEdge(boolean allow) {
mAttacher.setAllowParentInterceptOnEdge(allow);
}
@Override
@Deprecated
public void setMinScale(float minScale) {
setMinimumScale(minScale);
}
@Override
public void setMinimumScale(float minimumScale) {
mAttacher.setMinimumScale(minimumScale);
}
@Override
@Deprecated
public void setMidScale(float midScale) {
setMediumScale(midScale);
}
@Override
public void setMediumScale(float mediumScale) {
mAttacher.setMediumScale(mediumScale);
}
@Override
@Deprecated
public void setMaxScale(float maxScale) {
setMaximumScale(maxScale);
}
@Override
public void setMaximumScale(float maximumScale) {
mAttacher.setMaximumScale(maximumScale);
}
@Override
// setImageBitmap calls through to this method
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
if (null != mAttacher) {
mAttacher.update();
}
}
@Override
public void setImageResource(int resId) {
super.setImageResource(resId);
if (null != mAttacher) {
mAttacher.update();
}
}
@Override
public void setImageURI(Uri uri) {
super.setImageURI(uri);
if (null != mAttacher) {
mAttacher.update();
}
}
@Override
public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
mAttacher.setOnMatrixChangeListener(listener);
}
@Override
public void setOnLongClickListener(OnLongClickListener l) {
mAttacher.setOnLongClickListener(l);
}
@Override
public void setOnPhotoTapListener(OnPhotoTapListener listener) {
mAttacher.setOnPhotoTapListener(listener);
}
@Override
public OnPhotoTapListener getOnPhotoTapListener() {
return mAttacher.getOnPhotoTapListener();
}
@Override
public void setOnViewTapListener(OnViewTapListener listener) {
mAttacher.setOnViewTapListener(listener);
}
@Override
public OnViewTapListener getOnViewTapListener() {
return mAttacher.getOnViewTapListener();
}
@Override
public void setScale(float scale) {
mAttacher.setScale(scale);
}
@Override
public void setScale(float scale, boolean animate) {
mAttacher.setScale(scale, animate);
}
@Override
public void setScale(float scale, float focalX, float focalY,
boolean animate) {
mAttacher.setScale(scale, focalX, focalY, animate);
}
@Override
public void setScaleType(ScaleType scaleType) {
if (null != mAttacher) {
mAttacher.setScaleType(scaleType);
} else {
mPendingScaleType = scaleType;
}
}
@Override
public void setZoomable(boolean zoomable) {
mAttacher.setZoomable(zoomable);
}
@Override
public Bitmap getVisibleRectangleBitmap() {
return mAttacher.getVisibleRectangleBitmap();
}
@Override
public void setZoomTransitionDuration(int milliseconds) {
mAttacher.setZoomTransitionDuration(milliseconds);
}
@Override
protected void onDetachedFromWindow() {
mAttacher.cleanup();
super.onDetachedFromWindow();
}
}
16、
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.tianyu.photoview;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
import java.lang.ref.WeakReference;
import uk.co.senab.photoview.gestures.OnGestureListener;
import uk.co.senab.photoview.gestures.VersionedGestureDetector;
import uk.co.senab.photoview.log.LogManager;
import uk.co.senab.photoview.scrollerproxy.ScrollerProxy;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Matrix.ScaleToFit;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.FloatMath;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
/**
* 自定义图片视图
*
* @author Administrator
*
*/
public class PhotoViewAttacher implements IPhotoView, View.OnTouchListener,
OnGestureListener, ViewTreeObserver.OnGlobalLayoutListener {
// LOG标记
private static final String LOG_TAG = "PhotoViewAttacher";
// let debug flag be dynamic, but still Proguard can be used to remove from
// release builds
private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
/*
* AccelerateDecelerateInterpolator 在动画开始与结束的地方速率改变比较慢,在中间的时候加速
*
* AccelerateInterpolator 在动画开始的地方速率改变比较慢,然后开始加速
*
* AnticipateInterpolator 开始的时候向后然后向前甩
*
* AnticipateOvershootInterpolator 开始的时候向后然后向前甩一定值后返回最后的值
*
* BounceInterpolator 动画结束的时候弹起
*
* CycleInterpolator 动画循环播放特定的次数,速率改变沿着正弦曲线
*
* DecelerateInterpolator 在动画开始的地方快然后慢
*
* LinearInterpolator 以常量速率改变
*
* OvershootInterpolator 向前甩一定值后再回到原来位置
*/
static final Interpolator sInterpolator = new AccelerateDecelerateInterpolator();
// 变焦位置
int ZOOM_DURATION = DEFAULT_ZOOM_DURATION;
// 无边缘
static final int EDGE_NONE = -1;
// 左边缘
static final int EDGE_LEFT = 0;
// 右边缘
static final int EDGE_RIGHT = 1;
// 双向边缘
static final int EDGE_BOTH = 2;
// 最小缩放
private float mMinScale = DEFAULT_MIN_SCALE;
// 普通缩放
private float mMidScale = DEFAULT_MID_SCALE;
// 最大缩放
private float mMaxScale = DEFAULT_MAX_SCALE;
// 是否跟随默认的动画修饰效果
private boolean mAllowParentInterceptOnEdge = true;
/**
* 选择对焦级别
*
* @param minZoom
* @param midZoom
* @param maxZoom
*/
private static void checkZoomLevels(float minZoom, float midZoom,
float maxZoom) {
if (minZoom >= midZoom) {
throw new IllegalArgumentException(
"MinZoom has to be less than MidZoom");
} else if (midZoom >= maxZoom) {
throw new IllegalArgumentException(
"MidZoom has to be less than MaxZoom");
}
}
/**
*
* drawable是否存在
*
* @return true if the ImageView exists, and it's Drawable existss
*/
private static boolean hasDrawable(ImageView imageView) {
return null != imageView && null != imageView.getDrawable();
}
/**
* 是否支持缩放类型
*
* @return true if the ScaleType is supported.
*/
private static boolean isSupportedScaleType(final ScaleType scaleType) {
if (null == scaleType) {
return false;
}
switch (scaleType) {
case MATRIX:
throw new IllegalArgumentException(scaleType.name()
+ " is not supported in PhotoView");
default:
return true;
}
}
/**
* Set's the ImageView's ScaleType to Matrix.
*/
private static void setImageViewScaleTypeMatrix(ImageView imageView) {
/**
* PhotoView sets it's own ScaleType to Matrix, then diverts all calls
* setScaleType to this.setScaleType automatically.
*/
if (null != imageView && !(imageView instanceof IPhotoView)) {
if (!ScaleType.MATRIX.equals(imageView.getScaleType())) {
imageView.setScaleType(ScaleType.MATRIX);
}
}
}
// 弱引用
private WeakReference<ImageView> mImageView;
// Gesture Detectors
// 屏幕滑动
private GestureDetector mGestureDetector;
// 滑动接口
private uk.co.senab.photoview.gestures.GestureDetector mScaleDragDetector;
// These are set so we don't keep allocating them on the heap
private final Matrix mBaseMatrix = new Matrix();
private final Matrix mDrawMatrix = new Matrix();
private final Matrix mSuppMatrix = new Matrix();
private final RectF mDisplayRect = new RectF();
private final float[] mMatrixValues = new float[9];
// Listeners(监听事件)
// 分辨率改变
private OnMatrixChangedListener mMatrixChangeListener;
// 图片触摸
private OnPhotoTapListener mPhotoTapListener;
// view触摸
private OnViewTapListener mViewTapListener;
// 长按事件
private OnLongClickListener mLongClickListener;
private int mIvTop, mIvRight, mIvBottom, mIvLeft;
// 实时拉伸线程
private FlingRunnable mCurrentFlingRunnable;
// 滚动到边缘
private int mScrollEdge = EDGE_BOTH;
// 是否可以旋转检测
private final boolean mRotationDetectionEnabled = false;
// 是否对焦
private boolean mZoomEnabled;
// 缩放类型
private ScaleType mScaleType = ScaleType.FIT_CENTER;
public PhotoViewAttacher(ImageView imageView) {
mImageView = new WeakReference<ImageView>(imageView);
// 缓存
imageView.setDrawingCacheEnabled(true);
// 触摸
imageView.setOnTouchListener(this);
ViewTreeObserver observer = imageView.getViewTreeObserver();
if (null != observer)
// view 布局完成时调用,每次view改变时都会调用(测量上下左右的距离)
observer.addOnGlobalLayoutListener(this);
// Make sure we using MATRIX Scale Type
setImageViewScaleTypeMatrix(imageView);
if (imageView.isInEditMode()) {
return;
}
// Create Gesture Detectors...
mScaleDragDetector = VersionedGestureDetector.newInstance(
imageView.getContext(), this);
// 手势触摸事件
mGestureDetector = new GestureDetector(imageView.getContext(),
new GestureDetector.SimpleOnGestureListener() {
// forward long click listener
@Override
public void onLongPress(MotionEvent e) {
if (null != mLongClickListener) {
mLongClickListener.onLongClick(getImageView());
}
}
});
// 双击事件
mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(
this));
// Finally, update the UI so that we're zoomable
setZoomable(true);
}
/**
* Sets custom double tap listener, to intercept default given functions. To
* reset behavior to default, you can just pass in "null" or public field of
* PhotoViewAttacher.defaultOnDoubleTapListener <br>
*
* 手势双击事件
*
* @param newOnDoubleTapListener
* custom OnDoubleTapListener to be set on ImageView
*
*
*/
public void setOnDoubleTapListener(
GestureDetector.OnDoubleTapListener newOnDoubleTapListener) {
if (newOnDoubleTapListener != null)
this.mGestureDetector
.setOnDoubleTapListener(newOnDoubleTapListener);
else
this.mGestureDetector
.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));
}
/**
* 能对焦
*/
@Override
public boolean canZoom() {
return mZoomEnabled;
}
/**
* Clean-up the resources attached to this object. This needs to be called
* when the ImageView is no longer used. A good example is from
* {@link android.view.View#onDetachedFromWindow()} or from
* {@link android.app.Activity#onDestroy()}. This is automatically called if
* you are using {@link com.tianyu.photoview.PhotoView}.
*/
@SuppressWarnings("deprecation")
public void cleanup() {
if (null == mImageView) {
return; // cleanup already done
}
final ImageView imageView = mImageView.get();
if (null != imageView) {
// Remove this as a global layout listener
// 监听视图树的观察者
ViewTreeObserver observer = imageView.getViewTreeObserver();
if (null != observer && observer.isAlive()) {
observer.removeGlobalOnLayoutListener(this);
}
// Remove the ImageView's reference to this
imageView.setOnTouchListener(null);
// make sure a pending fling runnable won't be run
cancelFling();
}
if (null != mGestureDetector) {
mGestureDetector.setOnDoubleTapListener(null);
}
// Clear listeners too
// 清理监听事件
mMatrixChangeListener = null;
mPhotoTapListener = null;
mViewTapListener = null;
// Finally, clear ImageView
mImageView = null;
}
@Override
public RectF getDisplayRect() {
checkMatrixBounds();
return getDisplayRect(getDrawMatrix());
}
/**
* 设置屏幕分辨率
*/
@Override
public boolean setDisplayMatrix(Matrix finalMatrix) {
if (finalMatrix == null)
throw new IllegalArgumentException("Matrix cannot be null");
ImageView imageView = getImageView();
if (null == imageView)
return false;
if (null == imageView.getDrawable())
return false;
mSuppMatrix.set(finalMatrix);
setImageViewMatrix(getDrawMatrix());
checkMatrixBounds();
return true;
}
// 最后旋转
private float mLastRotation = 0;
/**
* 设置图像旋转角度
*/
@Override
public void setPhotoViewRotation(float degrees) {
degrees %= 360;
mSuppMatrix.postRotate(mLastRotation - degrees);
mLastRotation = degrees;
checkAndDisplayMatrix();
}
public ImageView getImageView() {
ImageView imageView = null;
if (null != mImageView) {
imageView = mImageView.get();
}
// If we don't have an ImageView, call cleanup()
if (null == imageView) {
cleanup();
Log.i(LOG_TAG,
"ImageView no longer exists. You should not use this PhotoViewAttacher any more.");
}
return imageView;
}
@Override
@Deprecated
public float getMinScale() {
return getMinimumScale();
}
@Override
public float getMinimumScale() {
return mMinScale;
}
@Override
@Deprecated
public float getMidScale() {
return getMediumScale();
}
@Override
public float getMediumScale() {
return mMidScale;
}
@Override
@Deprecated
public float getMaxScale() {
return getMaximumScale();
}
@Override
public float getMaximumScale() {
return mMaxScale;
}
/**
* 获得缩放
*/
@Override
public float getScale() {
return FloatMath.sqrt((float) Math.pow(
getValue(mSuppMatrix, Matrix.MSCALE_X), 2)
+ (float) Math.pow(getValue(mSuppMatrix, Matrix.MSKEW_Y), 2));
}
/**
* 获取缩放类型
*/
@Override
public ScaleType getScaleType() {
return mScaleType;
}
/**
* 拖动
*/
@Override
public void onDrag(float dx, float dy) {
if (mScaleDragDetector.isScaling()) {
return; // Do not drag if we are already scaling
}
if (DEBUG) {
LogManager.getLogger().d(LOG_TAG,
String.format("onDrag: dx: %.2f. dy: %.2f", dx, dy));
}
ImageView imageView = getImageView();
mSuppMatrix.postTranslate(dx, dy);
checkAndDisplayMatrix();
/**
* Here we decide whether to let the ImageView's parent to start taking
* over the touch event.
*
* First we check whether this function is enabled. We never want the
* parent to take over if we're scaling. We then check the edge we're
* on, and the direction of the scroll (i.e. if we're pulling against
* the edge, aka 'overscrolling', let the parent take over).
*/
ViewParent parent = imageView.getParent();
if (mAllowParentInterceptOnEdge) {
if (mScrollEdge == EDGE_BOTH
|| (mScrollEdge == EDGE_LEFT && dx >= 1f)
|| (mScrollEdge == EDGE_RIGHT && dx <= -1f)) {
if (null != parent)
parent.requestDisallowInterceptTouchEvent(false);
}
} else {
if (null != parent) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
}
/**
* 拖拽
*/
@Override
public void onFling(float startX, float startY, float velocityX,
float velocityY) {
if (DEBUG) {
LogManager.getLogger().d(
LOG_TAG,
"onFling. sX: " + startX + " sY: " + startY + " Vx: "
+ velocityX + " Vy: " + velocityY);
}
ImageView imageView = getImageView();
mCurrentFlingRunnable = new FlingRunnable(imageView.getContext());
mCurrentFlingRunnable
.fling(getImageViewWidth(imageView),
getImageViewHeight(imageView), (int) velocityX,
(int) velocityY);
imageView.post(mCurrentFlingRunnable);
}
/**
* 软键盘的弹出隐藏
*/
@Override
public void onGlobalLayout() {
ImageView imageView = getImageView();
if (null != imageView) {
if (mZoomEnabled) {
final int top = imageView.getTop();
final int right = imageView.getRight();
final int bottom = imageView.getBottom();
final int left = imageView.getLeft();
/**
* We need to check whether the ImageView's bounds have changed.
* This would be easier if we targeted API 11+ as we could just
* use View.OnLayoutChangeListener. Instead we have to replicate
* the work, keeping track of the ImageView's bounds and then
* checking if the values change.
*/
if (top != mIvTop || bottom != mIvBottom || left != mIvLeft
|| right != mIvRight) {
// Update our base matrix, as the bounds have changed
updateBaseMatrix(imageView.getDrawable());
// Update values as something has changed
mIvTop = top;
mIvRight = right;
mIvBottom = bottom;
mIvLeft = left;
}
} else {
updateBaseMatrix(imageView.getDrawable());
}
}
}
/**
* 缩放
*/
@Override
public void onScale(float scaleFactor, float focusX, float focusY) {
if (DEBUG) {
LogManager.getLogger().d(
LOG_TAG,
String.format("onScale: scale: %.2f. fX: %.2f. fY: %.2f",
scaleFactor, focusX, focusY));
}
if (getScale() < mMaxScale || scaleFactor < 1f) {
mSuppMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
checkAndDisplayMatrix();
}
}
/**
* 触摸
*/
@Override
public boolean onTouch(View v, MotionEvent ev) {
boolean handled = false;
if (mZoomEnabled && hasDrawable((ImageView) v)) {
ViewParent parent = v.getParent();
switch (ev.getAction()) {
case ACTION_DOWN:
// First, disable the Parent from intercepting the touch
// event
if (null != parent)
parent.requestDisallowInterceptTouchEvent(true);
else
Log.i(LOG_TAG, "onTouch getParent() returned null");
// If we're flinging, and the user presses down, cancel
// fling
cancelFling();
break;
case ACTION_CANCEL:
case ACTION_UP:
// If the user has zoomed less than min scale, zoom back
// to min scale
if (getScale() < mMinScale) {
RectF rect = getDisplayRect();
if (null != rect) {
v.post(new AnimatedZoomRunnable(getScale(), mMinScale,
rect.centerX(), rect.centerY()));
handled = true;
}
}
break;
}
// Try the Scale/Drag detector
if (null != mScaleDragDetector
&& mScaleDragDetector.onTouchEvent(ev)) {
handled = true;
}
// Check to see if the user double tapped
if (null != mGestureDetector && mGestureDetector.onTouchEvent(ev)) {
handled = true;
}
}
return handled;
}
/**
* 跟随默认的动画显示效果
*/
@Override
public void setAllowParentInterceptOnEdge(boolean allow) {
mAllowParentInterceptOnEdge = allow;
}
@Override
@Deprecated
public void setMinScale(float minScale) {
setMinimumScale(minScale);
}
/**
* 最小缩放
*/
@Override
public void setMinimumScale(float minimumScale) {
checkZoomLevels(minimumScale, mMidScale, mMaxScale);
mMinScale = minimumScale;
}
@Override
@Deprecated
public void setMidScale(float midScale) {
setMediumScale(midScale);
}
/**
* 普通缩放
*/
@Override
public void setMediumScale(float mediumScale) {
checkZoomLevels(mMinScale, mediumScale, mMaxScale);
mMidScale = mediumScale;
}
@Override
@Deprecated
public void setMaxScale(float maxScale) {
setMaximumScale(maxScale);
}
/**
* 最大缩放
*/
@Override
public void setMaximumScale(float maximumScale) {
checkZoomLevels(mMinScale, mMidScale, maximumScale);
mMaxScale = maximumScale;
}
@Override
public void setOnLongClickListener(OnLongClickListener listener) {
mLongClickListener = listener;
}
@Override
public void setOnMatrixChangeListener(OnMatrixChangedListener listener) {
mMatrixChangeListener = listener;
}
/**
* 单击图片
*/
@Override
public void setOnPhotoTapListener(OnPhotoTapListener listener) {
mPhotoTapListener = listener;
}
@Override
public OnPhotoTapListener getOnPhotoTapListener() {
return mPhotoTapListener;
}
@Override
public void setOnViewTapListener(OnViewTapListener listener) {
mViewTapListener = listener;
}
@Override
public OnViewTapListener getOnViewTapListener() {
return mViewTapListener;
}
@Override
public void setScale(float scale) {
setScale(scale, false);
}
@Override
public void setScale(float scale, boolean animate) {
ImageView imageView = getImageView();
if (null != imageView) {
setScale(scale, (imageView.getRight()) / 2,
(imageView.getBottom()) / 2, animate);
}
}
@Override
public void setScale(float scale, float focalX, float focalY,
boolean animate) {
ImageView imageView = getImageView();
if (null != imageView) {
// Check to see if the scale is within bounds
if (scale < mMinScale || scale > mMaxScale) {
LogManager
.getLogger()
.i(LOG_TAG,
"Scale must be within the range of minScale and maxScale");
return;
}
if (animate) {
imageView.post(new AnimatedZoomRunnable(getScale(), scale,
focalX, focalY));
} else {
mSuppMatrix.setScale(scale, scale, focalX, focalY);
checkAndDisplayMatrix();
}
}
}
@Override
public void setScaleType(ScaleType scaleType) {
if (isSupportedScaleType(scaleType) && scaleType != mScaleType) {
mScaleType = scaleType;
// Finally update
update();
}
}
/**
* 设置对焦
*/
@Override
public void setZoomable(boolean zoomable) {
mZoomEnabled = zoomable;
update();
}
/**
* 更新
*/
public void update() {
ImageView imageView = getImageView();
if (null != imageView) {
if (mZoomEnabled) {
// Make sure we using MATRIX Scale Type
setImageViewScaleTypeMatrix(imageView);
// Update the base matrix using the current drawable
updateBaseMatrix(imageView.getDrawable());
} else {
// Reset the Matrix...
resetMatrix();
}
}
}
@Override
public Matrix getDisplayMatrix() {
return new Matrix(getDrawMatrix());
}
public Matrix getDrawMatrix() {
mDrawMatrix.set(mBaseMatrix);
mDrawMatrix.postConcat(mSuppMatrix);
return mDrawMatrix;
}
/**
* 取消线程
*/
private void cancelFling() {
if (null != mCurrentFlingRunnable) {
mCurrentFlingRunnable.cancelFling();
mCurrentFlingRunnable = null;
}
}
/**
* Helper method that simply checks the Matrix, and then displays the result
*/
private void checkAndDisplayMatrix() {
if (checkMatrixBounds()) {
setImageViewMatrix(getDrawMatrix());
}
}
private void checkImageViewScaleType() {
ImageView imageView = getImageView();
/**
* PhotoView's getScaleType() will just divert to this.getScaleType() so
* only call if we're not attached to a PhotoView.
*/
if (null != imageView && !(imageView instanceof IPhotoView)) {
if (!ScaleType.MATRIX.equals(imageView.getScaleType())) {
throw new IllegalStateException(
"The ImageView's ScaleType has been changed since attaching a PhotoViewAttacher");
}
}
}
/**
* 选择分辨率占用大小
*
* @return
*/
private boolean checkMatrixBounds() {
final ImageView imageView = getImageView();
if (null == imageView) {
return false;
}
final RectF rect = getDisplayRect(getDrawMatrix());
if (null == rect) {
return false;
}
final float height = rect.height(), width = rect.width();
float deltaX = 0, deltaY = 0;
// 高
final int viewHeight = getImageViewHeight(imageView);
if (height <= viewHeight) {
switch (mScaleType) {
case FIT_START:
deltaY = -rect.top;
break;
case FIT_END:
deltaY = viewHeight - height - rect.top;
break;
default:
deltaY = (viewHeight - height) / 2 - rect.top;
break;
}
} else if (rect.top > 0) {
deltaY = -rect.top;
} else if (rect.bottom < viewHeight) {
deltaY = viewHeight - rect.bottom;
}
// 宽
final int viewWidth = getImageViewWidth(imageView);
if (width <= viewWidth) {
switch (mScaleType) {
case FIT_START:
deltaX = -rect.left;
break;
case FIT_END:
deltaX = viewWidth - width - rect.left;
break;
default:
deltaX = (viewWidth - width) / 2 - rect.left;
break;
}
mScrollEdge = EDGE_BOTH;
} else if (rect.left > 0) {
mScrollEdge = EDGE_LEFT;
deltaX = -rect.left;
} else if (rect.right < viewWidth) {
deltaX = viewWidth - rect.right;
mScrollEdge = EDGE_RIGHT;
} else {
mScrollEdge = EDGE_NONE;
}
// Finally actually translate the matrix
mSuppMatrix.postTranslate(deltaX, deltaY);
return true;
}
/**
* Helper method that maps the supplied Matrix to the current Drawable
*
* @param matrix
* - Matrix to map Drawable against
* @return RectF - Displayed Rectangle
*/
private RectF getDisplayRect(Matrix matrix) {
ImageView imageView = getImageView();
if (null != imageView) {
Drawable d = imageView.getDrawable();
if (null != d) {
mDisplayRect.set(0, 0, d.getIntrinsicWidth(),
d.getIntrinsicHeight());
matrix.mapRect(mDisplayRect);
return mDisplayRect;
}
}
return null;
}
/**
* 获取长方形的图片显示
*/
@Override
public Bitmap getVisibleRectangleBitmap() {
ImageView imageView = getImageView();
return imageView == null ? null : imageView.getDrawingCache();
}
@Override
public void setZoomTransitionDuration(int milliseconds) {
if (milliseconds < 0)
milliseconds = DEFAULT_ZOOM_DURATION;
this.ZOOM_DURATION = milliseconds;
}
/**
* Helper method that 'unpacks' a Matrix and returns the required value
*
* @param matrix
* - Matrix to unpack
* @param whichValue
* - Which value from Matrix.M* to return
* @return float - returned value
*/
private float getValue(Matrix matrix, int whichValue) {
matrix.getValues(mMatrixValues);
return mMatrixValues[whichValue];
}
/**
* Resets the Matrix back to FIT_CENTER, and then displays it.s
*/
private void resetMatrix() {
mSuppMatrix.reset();
setImageViewMatrix(getDrawMatrix());
checkMatrixBounds();
}
/**
* 设置imageview的分辨率
*
* @param matrix
*/
private void setImageViewMatrix(Matrix matrix) {
ImageView imageView = getImageView();
if (null != imageView) {
checkImageViewScaleType();
imageView.setImageMatrix(matrix);
// Call MatrixChangedListener if needed
if (null != mMatrixChangeListener) {
RectF displayRect = getDisplayRect(matrix);
if (null != displayRect) {
mMatrixChangeListener.onMatrixChanged(displayRect);
}
}
}
}
/**
* Calculate Matrix for FIT_CENTER
*
* @param d
* - Drawable being displayed
*/
private void updateBaseMatrix(Drawable d) {
ImageView imageView = getImageView();
if (null == imageView || null == d) {
return;
}
final float viewWidth = getImageViewWidth(imageView);
final float viewHeight = getImageViewHeight(imageView);
final int drawableWidth = d.getIntrinsicWidth();
final int drawableHeight = d.getIntrinsicHeight();
mBaseMatrix.reset();
final float widthScale = viewWidth / drawableWidth;
final float heightScale = viewHeight / drawableHeight;
if (mScaleType == ScaleType.CENTER) {
mBaseMatrix.postTranslate((viewWidth - drawableWidth) / 2F,
(viewHeight - drawableHeight) / 2F);
} else if (mScaleType == ScaleType.CENTER_CROP) {
float scale = Math.max(widthScale, heightScale);
mBaseMatrix.postScale(scale, scale);
mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
(viewHeight - drawableHeight * scale) / 2F);
} else if (mScaleType == ScaleType.CENTER_INSIDE) {
float scale = Math.min(1.0f, Math.min(widthScale, heightScale));
mBaseMatrix.postScale(scale, scale);
mBaseMatrix.postTranslate((viewWidth - drawableWidth * scale) / 2F,
(viewHeight - drawableHeight * scale) / 2F);
} else {
RectF mTempSrc = new RectF(0, 0, drawableWidth, drawableHeight);
RectF mTempDst = new RectF(0, 0, viewWidth, viewHeight);
switch (mScaleType) {
case FIT_CENTER:
mBaseMatrix
.setRectToRect(mTempSrc, mTempDst, ScaleToFit.CENTER);
break;
case FIT_START:
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.START);
break;
case FIT_END:
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.END);
break;
case FIT_XY:
mBaseMatrix.setRectToRect(mTempSrc, mTempDst, ScaleToFit.FILL);
break;
default:
break;
}
}
resetMatrix();
}
private int getImageViewWidth(ImageView imageView) {
if (null == imageView)
return 0;
return imageView.getWidth() - imageView.getPaddingLeft()
- imageView.getPaddingRight();
}
private int getImageViewHeight(ImageView imageView) {
if (null == imageView)
return 0;
return imageView.getHeight() - imageView.getPaddingTop()
- imageView.getPaddingBottom();
}
/**
* Interface definition for a callback to be invoked when the internal
* Matrix has changed for this View.
*
* @author Chris Banes
*/
public static interface OnMatrixChangedListener {
/**
* Callback for when the Matrix displaying the Drawable has changed.
* This could be because the View's bounds have changed, or the user has
* zoomed.
*
* @param rect
* - Rectangle displaying the Drawable's new bounds.
*/
void onMatrixChanged(RectF rect);
}
/**
* Interface definition for a callback to be invoked when the Photo is
* tapped with a single tap.
*
* @author Chris Banes
*/
public static interface OnPhotoTapListener {
/**
* A callback to receive where the user taps on a photo. You will only
* receive a callback if the user taps on the actual photo, tapping on
* 'whitespace' will be ignored.
*
* @param view
* - View the user tapped.
* @param x
* - where the user tapped from the of the Drawable, as
* percentage of the Drawable width.
* @param y
* - where the user tapped from the top of the Drawable, as
* percentage of the Drawable height.
*/
void onPhotoTap(View view, float x, float y);
}
/**
* Interface definition for a callback to be invoked when the ImageView is
* tapped with a single tap.
*
* @author Chris Banes
*/
public static interface OnViewTapListener {
/**
* A callback to receive where the user taps on a ImageView. You will
* receive a callback if the user taps anywhere on the view, tapping on
* 'whitespace' will not be ignored.
*
* @param view
* - View the user tapped.
* @param x
* - where the user tapped from the left of the View.
* @param y
* - where the user tapped from the top of the View.
*/
void onViewTap(View view, float x, float y);
}
/**
* 对焦线程
*
* @author Administrator
*
*/
private class AnimatedZoomRunnable implements Runnable {
private final float mFocalX, mFocalY;
private final long mStartTime;
private final float mZoomStart, mZoomEnd;
public AnimatedZoomRunnable(final float currentZoom,
final float targetZoom, final float focalX, final float focalY) {
mFocalX = focalX;
mFocalY = focalY;
mStartTime = System.currentTimeMillis();
mZoomStart = currentZoom;
mZoomEnd = targetZoom;
}
@Override
public void run() {
ImageView imageView = getImageView();
if (imageView == null) {
return;
}
float t = interpolate();
float scale = mZoomStart + t * (mZoomEnd - mZoomStart);
float deltaScale = scale / getScale();
mSuppMatrix.postScale(deltaScale, deltaScale, mFocalX, mFocalY);
checkAndDisplayMatrix();
// We haven't hit our target scale yet, so post ourselves again
if (t < 1f) {
Compat.postOnAnimation(imageView, this);
}
}
private float interpolate() {
float t = 1f * (System.currentTimeMillis() - mStartTime)
/ ZOOM_DURATION;
t = Math.min(1f, t);
t = sInterpolator.getInterpolation(t);
return t;
}
}
/**
* 线程
*
* @author Administrator
*
*/
private class FlingRunnable implements Runnable {
private final ScrollerProxy mScroller;
private int mCurrentX, mCurrentY;
public FlingRunnable(Context context) {
mScroller = ScrollerProxy.getScroller(context);
}
public void cancelFling() {
if (DEBUG) {
LogManager.getLogger().d(LOG_TAG, "Cancel Fling");
}
mScroller.forceFinished(true);
}
public void fling(int viewWidth, int viewHeight, int velocityX,
int velocityY) {
final RectF rect = getDisplayRect();
if (null == rect) {
return;
}
final int startX = Math.round(-rect.left);
final int minX, maxX, minY, maxY;
if (viewWidth < rect.width()) {
minX = 0;
maxX = Math.round(rect.width() - viewWidth);
} else {
minX = maxX = startX;
}
final int startY = Math.round(-rect.top);
if (viewHeight < rect.height()) {
minY = 0;
maxY = Math.round(rect.height() - viewHeight);
} else {
minY = maxY = startY;
}
mCurrentX = startX;
mCurrentY = startY;
if (DEBUG) {
LogManager.getLogger().d(
LOG_TAG,
"fling. StartX:" + startX + " StartY:" + startY
+ " MaxX:" + maxX + " MaxY:" + maxY);
}
// If we actually can move, fling the scroller
if (startX != maxX || startY != maxY) {
mScroller.fling(startX, startY, velocityX, velocityY, minX,
maxX, minY, maxY, 0, 0);
}
}
@Override
public void run() {
if (mScroller.isFinished()) {
return; // remaining post that should not be handled
}
ImageView imageView = getImageView();
if (null != imageView && mScroller.computeScrollOffset()) {
final int newX = mScroller.getCurrX();
final int newY = mScroller.getCurrY();
if (DEBUG) {
LogManager.getLogger().d(
LOG_TAG,
"fling run(). CurrentX:" + mCurrentX + " CurrentY:"
+ mCurrentY + " NewX:" + newX + " NewY:"
+ newY);
}
mSuppMatrix.postTranslate(mCurrentX - newX, mCurrentY - newY);
setImageViewMatrix(getDrawMatrix());
mCurrentX = newX;
mCurrentY = newY;
// Post On animation
Compat.postOnAnimation(imageView, this);
}
}
}
}
17、手势监听器
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package uk.co.senab.photoview.gestures;
import android.content.Context;
import android.util.FloatMath;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
/**
* 手势检测
*
* @author Administrator
*
*/
public class CupcakeGestureDetector implements GestureDetector {
protected OnGestureListener mListener;
private static final String LOG_TAG = "CupcakeGestureDetector";
// X轴
float mLastTouchX;
// Y轴
float mLastTouchY;
final float mTouchSlop;
final float mMinimumVelocity;
@Override
public void setOnGestureListener(OnGestureListener listener) {
this.mListener = listener;
}
public CupcakeGestureDetector(Context context) {
final ViewConfiguration configuration = ViewConfiguration.get(context);
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mTouchSlop = configuration.getScaledTouchSlop();
}
private VelocityTracker mVelocityTracker;
private boolean mIsDragging;
float getActiveX(MotionEvent ev) {
return ev.getX();
}
float getActiveY(MotionEvent ev) {
return ev.getY();
}
@Override
public boolean isScaling() {
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {// 按下
mVelocityTracker = VelocityTracker.obtain();
if (null != mVelocityTracker) {
mVelocityTracker.addMovement(ev);
} else {
Log.i(LOG_TAG, "Velocity tracker is null");
}
mLastTouchX = getActiveX(ev);
mLastTouchY = getActiveY(ev);
mIsDragging = false;
break;
}
case MotionEvent.ACTION_MOVE: {// 移动
final float x = getActiveX(ev);
final float y = getActiveY(ev);
final float dx = x - mLastTouchX, dy = y - mLastTouchY;
if (!mIsDragging) {
// Use Pythagoras to see if drag length is larger than
// touch slop
mIsDragging = FloatMath.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop;
}
if (mIsDragging) {
mListener.onDrag(dx, dy);
mLastTouchX = x;
mLastTouchY = y;
if (null != mVelocityTracker) {
mVelocityTracker.addMovement(ev);
}
}
break;
}
case MotionEvent.ACTION_CANCEL: {// 取消
// Recycle Velocity Tracker
if (null != mVelocityTracker) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
}
case MotionEvent.ACTION_UP: {// 抬起
if (mIsDragging) {
if (null != mVelocityTracker) {
mLastTouchX = getActiveX(ev);
mLastTouchY = getActiveY(ev);
// Compute velocity within the last 1000ms
mVelocityTracker.addMovement(ev);
mVelocityTracker.computeCurrentVelocity(1000);
final float vX = mVelocityTracker.getXVelocity(), vY = mVelocityTracker
.getYVelocity();
// If the velocity is greater than minVelocity, call
// listener
if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) {
mListener.onFling(mLastTouchX, mLastTouchY, -vX, -vY);
}
}
}
// Recycle Velocity Tracker
if (null != mVelocityTracker) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
}
}
return true;
}
}
18、
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package uk.co.senab.photoview.gestures;
import com.tianyu.photoview.Compat;
import android.annotation.TargetApi;
import android.content.Context;
import android.view.MotionEvent;
@TargetApi(5)
public class EclairGestureDetector extends CupcakeGestureDetector {
// 无效的ID
private static final int INVALID_POINTER_ID = -1;
// 手指ID
private int mActivePointerId = INVALID_POINTER_ID;
// 手势下标
private int mActivePointerIndex = 0;
public EclairGestureDetector(Context context) {
super(context);
}
@Override
float getActiveX(MotionEvent ev) {
try {
return ev.getX(mActivePointerIndex);
} catch (Exception e) {
return ev.getX();
}
}
@Override
float getActiveY(MotionEvent ev) {
try {
return ev.getY(mActivePointerIndex);
} catch (Exception e) {
return ev.getY();
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:// 按下
mActivePointerId = ev.getPointerId(0);
break;
case MotionEvent.ACTION_CANCEL:// 取消
case MotionEvent.ACTION_UP:// 抬起
mActivePointerId = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:// 手指抬起
// Ignore deprecation, ACTION_POINTER_ID_MASK and
// ACTION_POINTER_ID_SHIFT has same value and are deprecated
// You can have either deprecation or lint target api warning
final int pointerIndex = Compat.getPointerIndex(ev.getAction());
final int pointerId = ev.getPointerId(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;
mActivePointerId = ev.getPointerId(newPointerIndex);
mLastTouchX = ev.getX(newPointerIndex);
mLastTouchY = ev.getY(newPointerIndex);
}
break;
}
mActivePointerIndex = ev
.findPointerIndex(mActivePointerId != INVALID_POINTER_ID ? mActivePointerId
: 0);
return super.onTouchEvent(ev);
}
}
19、
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package uk.co.senab.photoview.gestures;
import android.annotation.TargetApi;
import android.content.Context;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
@TargetApi(8)
public class FroyoGestureDetector extends EclairGestureDetector {
protected final ScaleGestureDetector mDetector;
public FroyoGestureDetector(Context context) {
super(context);
ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scaleFactor = detector.getScaleFactor();
if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor))
return false;
mListener.onScale(scaleFactor,
detector.getFocusX(), detector.getFocusY());
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
// NO-OP
}
};
mDetector = new ScaleGestureDetector(context, mScaleListener);
}
@Override
public boolean isScaling() {
return mDetector.isInProgress();
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
mDetector.onTouchEvent(ev);
return super.onTouchEvent(ev);
}
}
20
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package uk.co.senab.photoview.gestures;
import android.view.MotionEvent;
/**
* 滑动接口
*
* @author Administrator
*
*/
public interface GestureDetector {
public boolean onTouchEvent(MotionEvent ev);
public boolean isScaling();
public void setOnGestureListener(OnGestureListener listener);
}
21、
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package uk.co.senab.photoview.gestures;
/**
* 手势接口
*
* @author Administrator
*
*/
public interface OnGestureListener {
// 拖拽
public void onDrag(float dx, float dy);
// 拖
public void onFling(float startX, float startY, float velocityX,
float velocityY);
// 拉伸
public void onScale(float scaleFactor, float focusX, float focusY);
}
23、
package uk.co.senab.photoview.gestures;
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
import android.content.Context;
import android.os.Build;
/**
* 版本
*
* @author Administrator
*
*/
public final class VersionedGestureDetector {
public static GestureDetector newInstance(Context context,
OnGestureListener listener) {
// SDK版本
final int sdkVersion = Build.VERSION.SDK_INT;
GestureDetector detector;
if (sdkVersion < Build.VERSION_CODES.ECLAIR) {
detector = new CupcakeGestureDetector(context);
} else if (sdkVersion < Build.VERSION_CODES.FROYO) {
detector = new EclairGestureDetector(context);
} else {
detector = new FroyoGestureDetector(context);
}
detector.setOnGestureListener(listener);
return detector;
}
}
25、LOG类
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package uk.co.senab.photoview.log;
/**
* interface for a logger class to replace the static calls to {@link android.util.Log}
*/
public interface Logger {
/**
* Send a {@link android.util.Log#VERBOSE} log message.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
int v(String tag, String msg);
/**
* Send a {@link android.util.Log#VERBOSE} log message and log the exception.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
int v(String tag, String msg, Throwable tr);
/**
* Send a {@link android.util.Log#DEBUG} log message.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
int d(String tag, String msg);
/**
* Send a {@link android.util.Log#DEBUG} log message and log the exception.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
int d(String tag, String msg, Throwable tr);
/**
* Send an {@link android.util.Log#INFO} log message.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
int i(String tag, String msg);
/**
* Send a {@link android.util.Log#INFO} log message and log the exception.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
int i(String tag, String msg, Throwable tr);
/**
* Send a {@link android.util.Log#WARN} log message.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
int w(String tag, String msg);
/**
* Send a {@link android.util.Log#WARN} log message and log the exception.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
int w(String tag, String msg, Throwable tr);
/**
* Send an {@link android.util.Log#ERROR} log message.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
*/
int e(String tag, String msg);
/**
* Send a {@link android.util.Log#ERROR} log message and log the exception.
*
* @param tag Used to identify the source of a log message. It usually identifies
* the class or activity where the log call occurs.
* @param msg The message you would like logged.
* @param tr An exception to log
*/
int e(String tag, String msg, Throwable tr);
}
26、
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package uk.co.senab.photoview.log;
import android.util.Log;
/**
* 默认LOG
*
* Helper class to redirect {@link LogManager#logger} to {@link Log}
*/
public class LoggerDefault implements Logger {
@Override
public int v(String tag, String msg) {
return Log.v(tag, msg);
}
@Override
public int v(String tag, String msg, Throwable tr) {
return Log.v(tag, msg, tr);
}
@Override
public int d(String tag, String msg) {
return Log.d(tag, msg);
}
@Override
public int d(String tag, String msg, Throwable tr) {
return Log.d(tag, msg, tr);
}
@Override
public int i(String tag, String msg) {
return Log.i(tag, msg);
}
@Override
public int i(String tag, String msg, Throwable tr) {
return Log.i(tag, msg, tr);
}
@Override
public int w(String tag, String msg) {
return Log.w(tag, msg);
}
@Override
public int w(String tag, String msg, Throwable tr) {
return Log.w(tag, msg, tr);
}
@Override
public int e(String tag, String msg) {
return Log.e(tag, msg);
}
@Override
public int e(String tag, String msg, Throwable tr) {
return Log.e(tag, msg, tr);
}
}
27、
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package uk.co.senab.photoview.log;
import android.util.Log;
/**
* class that holds the {@link Logger} for this library, defaults to
* {@link LoggerDefault} to send logs to android {@link Log}
*/
public final class LogManager {
private static Logger logger = new LoggerDefault();
public static void setLogger(Logger newLogger) {
logger = newLogger;
}
public static Logger getLogger() {
return logger;
}
}
28、
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package uk.co.senab.photoview.scrollerproxy;
import android.annotation.TargetApi;
import android.content.Context;
import android.widget.OverScroller;
/**
* 滚动
*
* @author Administrator
*
*/
@TargetApi(9)
public class GingerScroller extends ScrollerProxy {
// 滑动助手
protected final OverScroller mScroller;
// 首次滚动
private boolean mFirstScroll = false;
public GingerScroller(Context context) {
mScroller = new OverScroller(context);
}
/**
* 计算滚动偏移量
*/
@Override
public boolean computeScrollOffset() {
// Workaround for first scroll returning 0 for the direction of the edge
// it hits.
// Simply recompute values.
if (mFirstScroll) {
mScroller.computeScrollOffset();
mFirstScroll = false;
}
return mScroller.computeScrollOffset();
}
@Override
public void fling(int startX, int startY, int velocityX, int velocityY,
int minX, int maxX, int minY, int maxY, int overX, int overY) {
mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY,
maxY, overX, overY);
}
/**
* 推动完成
*/
@Override
public void forceFinished(boolean finished) {
mScroller.forceFinished(finished);
}
/**
* 是否完成
*/
@Override
public boolean isFinished() {
return mScroller.isFinished();
}
/**
* X轴实时位置
*/
@Override
public int getCurrX() {
return mScroller.getCurrX();
}
/**
* Y轴实时位置
*/
@Override
public int getCurrY() {
return mScroller.getCurrY();
}
}
29、
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package uk.co.senab.photoview.scrollerproxy;
import android.annotation.TargetApi;
import android.content.Context;
@TargetApi(14)
public class IcsScroller extends GingerScroller {
public IcsScroller(Context context) {
super(context);
}
@Override
public boolean computeScrollOffset() {
return mScroller.computeScrollOffset();
}
}
30、
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package uk.co.senab.photoview.scrollerproxy;
import android.content.Context;
import android.widget.Scroller;
public class PreGingerScroller extends ScrollerProxy {
private final Scroller mScroller;
public PreGingerScroller(Context context) {
mScroller = new Scroller(context);
}
@Override
public boolean computeScrollOffset() {
return mScroller.computeScrollOffset();
}
@Override
public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY,
int overX, int overY) {
mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
}
@Override
public void forceFinished(boolean finished) {
mScroller.forceFinished(finished);
}
public boolean isFinished() {
return mScroller.isFinished();
}
@Override
public int getCurrX() {
return mScroller.getCurrX();
}
@Override
public int getCurrY() {
return mScroller.getCurrY();
}
}
31、
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package uk.co.senab.photoview.scrollerproxy;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
/**
* 滚动代理
*
* @author Administrator
*
*/
public abstract class ScrollerProxy {
public static ScrollerProxy getScroller(Context context) {
if (VERSION.SDK_INT < VERSION_CODES.GINGERBREAD) {
return new PreGingerScroller(context);
} else if (VERSION.SDK_INT < VERSION_CODES.ICE_CREAM_SANDWICH) {
return new GingerScroller(context);
} else {
return new IcsScroller(context);
}
}
// 计算滚动偏移量
public abstract boolean computeScrollOffset();
public abstract void fling(int startX, int startY, int velocityX,
int velocityY, int minX, int maxX, int minY, int maxY, int overX,
int overY);
public abstract void forceFinished(boolean finished);
public abstract boolean isFinished();
public abstract int getCurrX();
public abstract int getCurrY();
}
二、资源文件
1、
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="6dp" >
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="暂无数据" />
</RelativeLayout>
2、
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/album_image"
android:layout_width="80dp"
android:layout_height="80dp"
android:scaleType="centerCrop"
android:src="@drawable/empty_photo" />
3、
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="@color/black"
android:layout_height="match_parent" >
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:contentDescription="@string/app_name"
android:scaleType="centerCrop" />
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</FrameLayout>
4、
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.tianyu.demo.image.HackyViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black" />
<TextView
android:id="@+id/indicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@android:color/transparent"
android:gravity="center"
android:text="@string/viewpager_indicator"
android:textColor="@android:color/white"
android:textSize="18sp" />
</FrameLayout>
5、
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="6dp" >
<ImageView
android:id="@+id/avator"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/empty_photo" />
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/avator"
android:textColor="#576B95"
android:textSize="18sp"
android:text="张三" />
<TextView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/name"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/avator"
android:text="content" />
<com.tianyu.demo.image.NoScrollGridView
android:id="@+id/gridView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/content"
android:layout_marginLeft="10dp"
android:layout_toRightOf="@id/avator"
android:horizontalSpacing="1dp"
android:numColumns="3"
android:stretchMode="columnWidth"
android:visibility="gone"
android:verticalSpacing="1dp" />
</RelativeLayout>