看博文之前,希望大家先打开自己的微信点到朋友圈中去,仔细观察是不是发现朋友圈里的有个“九宫格”的图片区域,点击图片又会跳到图片的详细查看页面,并且支持图片的滑动和缩放?这个功能是不是很常用呢?!那么我今天正好做了这个Demo,下面为大家讲解一下。首先按照惯例先看一下效果图吧,尤其不会录制gif动画(哎~没办法,模拟器不支持多点触控,刚好我的手机又没有Root,不能录屏,悲催啊,大家见谅,想要看真实效果的话,烦请移到博文最下方,点击下载源码,运行后再看效果哈~~),这里先就拿几张静态的图片顶替一下好了。见谅!
主页ListView的效果: 点击九宫格图片跳转到大图 多点触控,缩放图片
效果嘛,将就着看吧!实在看不明白就想想微信朋友圈,或者拖到下方,点击下载源码!这里,首先分析一下主界面吧,布局都是很简单的,主界面仅仅就是一个ListView的控件,ListView的Item上值得注意的是,Item上包含了一个GridView,这个GridView呗用作实现“九宫格”的效果,主界面布局就是一个ListView,这里不说了,我们先来看看ListView的Item的布局吧,以下是item_list.xml
1
2
3
4
5
6
7
8
9
10
11
12
|
<!--?xml version=
1.0
encoding=utf-
8
?-->
<relativelayout android:layout_height=
"match_parent"
android:layout_width=
"match_parent"
android:paddingbottom=
"5dp"
android:paddingtop=
"5dp"
xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<imageview android:background=
"@drawable/ic_launcher"
android:id=
"@+id/iv_avatar"
android:layout_height=
"50dp"
android:layout_width=
"50dp"
android:scaletype=
"centerCrop"
>
<textview android:id=
"@+id/tv_title"
android:layout_height=
"wrap_content"
android:layout_marginleft=
"5dp"
android:layout_torightof=
"@id/iv_avatar"
android:layout_width=
"wrap_content"
android:text=
"爷,今天心情好!"
android:textsize=
"16sp"
>
<textview android:id=
"@+id/tv_content"
android:layout_below=
"@+id/tv_title"
android:layout_height=
"wrap_content"
android:layout_marginleft=
"5dp"
android:layout_margintop=
"3dp"
android:layout_torightof=
"@id/iv_avatar"
android:layout_width=
"wrap_content"
android:text=
"今天又是雾霾!"
android:textsize=
"16sp"
>
<com.example.imagedemo.noscrollgridview android:columnwidth=
"70dp"
android:gravity=
"center"
android:horizontalspacing=
"2.5dp"
android:id=
"@+id/gridview"
android:layout_below=
"@id/tv_content"
android:layout_height=
"wrap_content"
android:layout_marginleft=
"5dp"
android:layout_margintop=
"3dp"
android:layout_torightof=
"@id/iv_avatar"
android:layout_width=
"220dp"
android:numcolumns=
"3"
android:stretchmode=
"columnWidth"
android:verticalspacing=
"2.5dp"
>
</com.example.imagedemo.noscrollgridview></textview></textview></imageview></relativelayout>
|
好了,大家看到了,布局也是极其简单的,但是有个问题就是ListView嵌套进了GridView,那么就会出现一个问题,导致GridView显示的不全,那么该怎么解决这个问题呢?其实也简单,就是重写一个GridView,测量一下GridView的高度,再设置上去。具体解决方案请看上篇博文ListView嵌套GridView显示不全解决方法或者源码,如下NoScrollGridView.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
package
com.example.imagedemo;
import
android.content.Context;
import
android.util.AttributeSet;
import
android.widget.GridView;
/**
* 自定义的“九宫格”——用在显示帖子详情的图片集合 解决的问题:GridView显示不全,只显示了一行的图片,比较奇怪,尝试重写GridView来解决
*
* @author lichao
* @since 2014-10-16 16:41
*
*/
public
class
NoScrollGridView
extends
GridView {
public
NoScrollGridView(Context context) {
super
(context);
// TODO Auto-generated constructor stub
}
public
NoScrollGridView(Context context, AttributeSet attrs) {
super
(context, attrs);
// TODO Auto-generated constructor stub
}
public
NoScrollGridView(Context context, AttributeSet attrs,
int
defStyle) {
super
(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
@Override
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
// TODO Auto-generated method stub
int
expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >>
2
,
MeasureSpec.AT_MOST);
super
.onMeasure(widthMeasureSpec, expandSpec);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
class
ItemEntity {
private
String avatar;
// 用户头像URL
private
String title;
// 标题
private
String content;
// 内容
private
ArrayList<string> imageUrls;
// 九宫格图片的URL集合
public
ItemEntity(String avatar, String title, String content,
ArrayList<string> imageUrls) {
super
();
this
.avatar = avatar;
this
.title = title;
this
.content = content;
this
.imageUrls = imageUrls;
}
...
}</string></string>
|
好了,有了ListView,那么不可避免的就是做Item上的数据适配了。继承一个BaseAdapter,代码如下,都比较简单:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
/**
* 首页ListView的数据适配器
*
* @author Administrator
*
*/
public
class
ListItemAdapter
extends
BaseAdapter {
private
Context mContext;
private
ArrayList<itementity> items;
public
ListItemAdapter(Context ctx, ArrayList<itementity> items) {
this
.mContext = ctx;
this
.items = items;
}
@Override
public
int
getCount() {
return
items ==
null
?
0
: items.size();
}
@Override
public
Object getItem(
int
position) {
return
items.get(position);
}
@Override
public
long
getItemId(
int
position) {
return
position;
}
@Override
public
View getView(
int
position, View convertView, ViewGroup parent) {
ViewHolder holder;
if
(convertView ==
null
) {
holder =
new
ViewHolder();
convertView = View.inflate(mContext, R.layout.item_list,
null
);
holder.iv_avatar = (ImageView) convertView
.findViewById(R.id.iv_avatar);
holder.tv_title = (TextView) convertView
.findViewById(R.id.tv_title);
holder.tv_content = (TextView) convertView
.findViewById(R.id.tv_content);
holder.gridview = (NoScrollGridView) convertView
.findViewById(R.id.gridview);
convertView.setTag(holder);
}
else
{
holder = (ViewHolder) convertView.getTag();
}
ItemEntity itemEntity = items.get(position);
holder.tv_title.setText(itemEntity.getTitle());
holder.tv_content.setText(itemEntity.getContent());
// 使用ImageLoader加载网络图片
DisplayImageOptions options =
new
DisplayImageOptions.Builder()
//
.showImageOnLoading(R.drawable.ic_launcher)
// 加载中显示的默认图片
.showImageOnFail(R.drawable.ic_launcher)
// 设置加载失败的默认图片
.cacheInMemory(
true
)
// 内存缓存
.cacheOnDisk(
true
)
// sdcard缓存
.bitmapConfig(Config.RGB_565)
// 设置最低配置
.build();
//
ImageLoader.getInstance().displayImage(itemEntity.getAvatar(),
holder.iv_avatar, options);
final
ArrayList<string> imageUrls = itemEntity.getImageUrls();
if
(imageUrls ==
null
|| imageUrls.size() ==
0
) {
// 没有图片资源就隐藏GridView
holder.gridview.setVisibility(View.GONE);
}
else
{
holder.gridview.setAdapter(
new
NoScrollGridAdapter(mContext,
imageUrls));
}
// 点击回帖九宫格,查看大图
holder.gridview.setOnItemClickListener(
new
OnItemClickListener() {
@Override
public
void
onItemClick(AdapterView<!--?--> parent, View view,
int
position,
long
id) {
// TODO Auto-generated method stub
imageBrower(position, imageUrls);
}
});
return
convertView;
}
/**
* 打开图片查看器
*
* @param position
* @param urls2
*/
protected
void
imageBrower(
int
position, ArrayList<string> urls2) {
Intent intent =
new
Intent(mContext, ImagePagerActivity.
class
);
// 图片url,为了演示这里使用常量,一般从数据库中或网络中获取
intent.putExtra(ImagePagerActivity.EXTRA_IMAGE_URLS, urls2);
intent.putExtra(ImagePagerActivity.EXTRA_IMAGE_INDEX, position);
mContext.startActivity(intent);
}
/**
* listview组件复用,防止“卡顿”
*
* @author Administrator
*
*/
class
ViewHolder {
private
ImageView iv_avatar;
private
TextView tv_title;
private
TextView tv_content;
private
NoScrollGridView gridview;
}
}</string></string></itementity></itementity>
|
这里有需要解释的地方了,看看listview上的图片处理,由于图片都是从网络获取的,为了避免图片过多造成OOM,那么这里加载图片的时候必不可少的需要做内存优化,图片的优化方式有很多,我这里采取了最简单最直接得方式,使用了开源的ImageLoader这个图片加载框架,这个框架简直是太优秀了,减少了开发者一系列不必要而且时常会出现的麻烦,关于ImageLoader并不是本篇博文需要讲解的知识,关于ImageLoader,欢迎在GitHub主页上下载,地址是https://github.com/nostra13/Android-Universal-Image-Loader,既然使用了ImageLoader这个框架,就不得不在程序上做一些初始化的操作,首先需要自定义一个全局的上下文Application类,将ImageLoader的相关属性初始化上去,直接看代码好了,见名知意:MyApplication.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
class
MyApplication
extends
Application {
@Override
public
void
onCreate() {
super
.onCreate();
DisplayImageOptions defaultOptions =
new
DisplayImageOptions.Builder()
//
.showImageForEmptyUri(R.drawable.ic_launcher)
//
.showImageOnFail(R.drawable.ic_launcher)
//
.cacheInMemory(
true
)
//
.cacheOnDisk(
true
)
//
.build();
//
ImageLoaderConfiguration config =
new
ImageLoaderConfiguration
//
.Builder(getApplicationContext())
//
.defaultDisplayImageOptions(defaultOptions)
//
.discCacheSize(
50
*
1024
*
1024
)
//
.discCacheFileCount(
100
)
// 缓存一百张图片
.writeDebugLogs()
//
.build();
//
ImageLoader.getInstance().init(config);
}
}
|
1
|
android:name=com.example.imagedemo.MyApplication
|
此外由于ImageLoader是网络获取图片,又需要本地sdcard缓存图片,所以需要加上一下的权限,这是Imageloader标准权限:
1
2
3
|
<uses-permission android:name=
"android.permission.INTERNET"
>
<uses-permission android:name=
"android.permission.WRITE_EXTERNAL_STORAGE"
>
<uses-permission android:name=
"android.permission.ACCESS_NETWORK_STATE"
></uses-permission></uses-permission></uses-permission>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
......
@Override
public
View getView(
int
position, View convertView, ViewGroup parent) {
View view = View.inflate(ctx, R.layout.item_gridview,
null
);
ImageView imageView = (ImageView) view.findViewById(R.id.iv_image);
DisplayImageOptions options =
new
DisplayImageOptions.Builder()
//
.cacheInMemory(
true
)
//
.cacheOnDisk(
true
)
//
.bitmapConfig(Config.RGB_565)
//
.build();
ImageLoader.getInstance().displayImage(imageUrls.get(position),
imageView, options);
return
view;
}
......
|
1
2
3
4
5
6
7
8
|
<!--?xml version=
1.0
encoding=utf-
8
?-->
<framelayout android:layout_height=
"match_parent"
android:layout_width=
"match_parent"
xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<com.example.imagedemo.hackyviewpager android:background=
"@android:color/black"
android:id=
"@+id/pager"
android:layout_height=
"match_parent"
android:layout_width=
"match_parent"
>
<textview android:background=
"@android:color/transparent"
android:gravity=
"center"
android:id=
"@+id/indicator"
android:layout_gravity=
"bottom"
android:layout_height=
"wrap_content"
android:layout_width=
"match_parent"
android:text=
"@string/viewpager_indicator"
android:textcolor=
"@android:color/white"
android:textsize=
"18sp"
>
</textview></com.example.imagedemo.hackyviewpager></framelayout>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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
;
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
/**
* 图片查看器
*/
public
class
ImagePagerActivity
extends
FragmentActivity {
private
static
final
String STATE_POSITION = STATE_POSITION;
public
static
final
String EXTRA_IMAGE_INDEX = image_index;
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
);
ArrayList<string> urls = getIntent().getStringArrayListExtra(
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());
}
private
class
ImagePagerAdapter
extends
FragmentStatePagerAdapter {
public
ArrayList<string> fileList;
public
ImagePagerAdapter(FragmentManager fm, ArrayList<string> fileList) {
super
(fm);
this
.fileList = fileList;
}
@Override
public
int
getCount() {
return
fileList ==
null
?
0
: fileList.size();
}
@Override
public
Fragment getItem(
int
position) {
String url = fileList.get(position);
return
ImageDetailFragment.newInstance(url);
}
}
}
</string></string></string>
|
已知图片查看的界面是继承自FragmentActivity的,所以支持显示的界面必须需要Fragment来实现,那么就自定义个Frangment吧,用这个Fragment来从url中获取图片资源,显示图片。image_detail_fragment.xml
1
2
3
4
5
6
7
8
|
<!--?xml version=
1.0
encoding=utf-
8
?-->
<framelayout android:background=
"@android:color/black"
android:layout_height=
"match_parent"
android:layout_width=
"match_parent"
xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<imageview android:adjustviewbounds=
"true"
android:contentdescription=
"@string/app_name"
android:id=
"@+id/image"
android:layout_height=
"match_parent"
android:layout_width=
"match_parent"
android:scaletype=
"centerCrop"
>
<progressbar android:id=
"@+id/loading"
android:layout_gravity=
"center"
android:layout_height=
"wrap_content"
android:layout_width=
"wrap_content"
android:visibility=
"gone"
>
</progressbar></imageview></framelayout>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
/**
* 单张图片显示Fragment
*/
public
class
ImageDetailFragment
extends
Fragment {
private
String mImageUrl;
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();
}
});
}
}
|
写到这里,此篇博文也宣告结束了。需要提出的是,我这里的图片查看器实现的图片的缩放效果使用的是开源组件PhotoView,关于PhotoView的github项目地址在这里,https://github.com/chrisbanes/PhotoView 需要点进去这个项目的网址,去下载源码,将源码全部拷贝到项目中来,使用也是相当方便的,demo如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
ImageView mImageView;
PhotoViewAttacher mAttacher;
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Any implementation of ImageView can be used!
mImageView = (ImageView) findViewById(R.id.iv_photo);
// Set the Drawable displayed
Drawable bitmap = getResources().getDrawable(R.drawable.wallpaper);
mImageView.setImageDrawable(bitmap);
// Attach a PhotoViewAttacher, which takes care of all of the zooming functionality.
mAttacher =
new
PhotoViewAttacher(mImageView);
}
// If you later call mImageView.setImageDrawable/setImageBitmap/setImageResource/etc then you just need to call
attacher.update();
|
刚开始这个图片查看器是我自己自定义View来实现的,其实需要实现图片的手势识别+多点触控+缩放,是可以使用矩阵Matrix来实现的,只不过这样显得特别的麻烦不说,而且极易出现BUG,这对于某些“急功近利”的项目来说,是个不好的兆头。所以,我这里摒弃了我用Matrix自定义的效果,改用github大牛为我们写好的开源组件,这样效率就上去了,大家也可以用Matrix自己去实现一下图片的多点触摸缩放的效果,关于Matrix的学习,请参加我以前的博文,Android自定义控件——3D画廊和图像矩阵。其实关于android上的图片缩放真没什么其它的方式,唯一能使用的还是Matrix这个类,不信先来瞧瞧Github大牛写的开源组件PhotoView是怎么实现的,查看以下部分源码:
1
2
3
4
5
6
|
// 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
];
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/**
* 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);
}
}
}
|