本文旨在教你打造一个真实的一个图片浏览器,并非写一堆链接用来测试;也算是用到Android基本的常用的知识,对于初学者来说是一个不错的练手demo;当然本文对于图片加载也有自己的一些见解,希望可以帮助到各位;
首先在开始讲解前要感谢一下github开源社区给了我很多帮助,相信不少人也从这里获取到自己想要的东西,当然如果有能力也是希望可以回馈社区;
再此列出在项目中用到的开源框架:
网络访问框架volley,这个大概大家都知道无需解释:
https://github.com/mcxiaoke/android-volley,详细学习请移至http://hukai.me/android-training-course-in-chinese/connectivity/volley/index.html
图片加载框架universal-image-loader这个谁用谁知道,当然也有不少陷阱:
https://github.com/nostra13/Android-Universal-Image-Loader,详细学习请移至http://codekk.com/open-source-project-analysis
下拉刷新框架android-Ultra-Pull-to-Refresh使用简单定制方便:
https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh
RippleEffect一个实现Material Design Ripple效果的库:
https://github.com/traex/RippleEffect,其实你仔细看他的实现相信你也可以做到
Stackblur高斯模糊框架,我在上一篇博客中有提到高斯模糊制作启动界面:
https://github.com/kikoso/android-stackblur
Gson解决对象和json的转换,方便简单快速:
https://github.com/google/gson,项目中虽然没有用到,因为字段比较少就省了,但是还是建议去用用
当然还得介绍一下android-open-project,进去看了就明白
https://github.com/Trinea/android-open-project
好了该感谢的都感谢完了,下面切入我们的正题;首先要解决的问题就是数据,app没有数据总是华而不实的,当然这里也不是要我们自己写服务器;
数据来源:百度图片api
http://image.baidu.com/channel/listjson?pn=0&rn=30&tag1=美女&tag2=全部,打开链接可以看到返回的json数据
参数详细介绍
pn:从第几项开始加载
rn:加载多少项
tag1:大分类(既然是美女图,那么这里就填美女喽)
tag2:小分类(例如:清纯,性感,气质...等等,可以去百度图片看看分类)
上面的链接解释就是:从第0项加载30个所有美女类型的图片;
如果要加载第二页呢?pn=30,其他不变;详细可以移至http://blog.csdn.net/yuanwofei/article/details/16343743,不过这篇博客有些时候了,可能用法上有误,pn不是页数;
项目结构及分包:
如果是为了测试其实倒也无所谓,但是这里我还是要做到一定的规范;正所谓写代码很容易,写好却不容易:
包结构图
- .util:存放app所需的工具类,这个模块不只是这一个项目会用到,大多数项目都可以复用。
- .view:自定义view存放目录
- .app:存放application类,app的入口用于初始化全局的一些东西
其他包就不多加解释了;
界面布局
欢迎界面
欢迎界面的高斯模糊效果请移至高斯模糊制作启动界面
主界面
头部是一个Toolbar,Toolbar右侧是一个menu用于弹出美女类型的popwindow;
内容区域是一个GirdView,附在下拉刷新布局中;
查看大图界面
一个viewpager,每个view是一个ImageView;
界面布局大致就是这样,是不是感觉很简单;
主界面:
Activity布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<include layout="@layout/toolbar" />
<FrameLayout
android:id="@+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Toolbar
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.xiaozhi.beautygallery"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
android:visibility="gone"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar" >
</android.support.v7.widget.Toolbar>
Fragment布局
<?xml version="1.0" encoding="utf-8"?>
<in.srain.cube.views.ptr.PtrFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:cube_ptr="http://schemas.android.com/apk/res-auto"
android:id="@+id/rotate_header_grid_view_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_material_dark"
cube_ptr:ptr_duration_to_close="200"
cube_ptr:ptr_duration_to_close_header="1000"
cube_ptr:ptr_keep_header_when_refresh="true"
cube_ptr:ptr_pull_to_fresh="false"
cube_ptr:ptr_ratio_of_header_height_to_refresh="1.2"
cube_ptr:ptr_resistance="1.7" >
<GridView
android:id="@+id/gridView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadingEdge="none"
android:focusable="false"
android:horizontalSpacing="@dimen/image_padding"
android:listSelector="@null"
android:numColumns="2"
android:verticalSpacing="@dimen/image_padding" />
</in.srain.cube.views.ptr.PtrFrameLayout>
GridView item布局
<?xml version="1.0" encoding="utf-8"?>
<com.xiaozhi.beautygallery.view.RippleView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ripple="http://schemas.android.com/apk/res-auto"
android:id="@+id/rect"
android:layout_width="match_parent"
android:layout_height="match_parent"
ripple:rv_alpha="50"
ripple:rv_rippleDuration="100"
ripple:rv_type="rectangle"
ripple:rv_zoom="true"
ripple:rv_zoomDuration="150" >
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="@dimen/image_height"
android:contentDescription="@string/app_name"
android:scaleType="centerCrop" />
</com.xiaozhi.beautygallery.view.RippleView>
GridView底部布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/foot_view_layout"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:layout_columnSpan="2"
android:gravity="center"
android:orientation="horizontal"
android:background="@drawable/footer_bg">
<ProgressBar
android:id="@+id/footer_loading"
style="@android:style/Widget.ProgressBar.Small.Inverse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<TextView
android:id="@+id/footview_text"
android:layout_width="wrap_content"
android:layout_height="50dip"
android:layout_gravity="center"
android:gravity="center"
android:textColor="@android:color/white"
android:textSize="15sp"
android:textStyle="bold" />
<Button
android:id="@+id/footview_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="5dp"
android:background="@null"
android:text="refresh" />
</LinearLayout>
Popwindow布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/pop_win_bg"
android:orientation="vertical"
android:padding="@dimen/image_padding" >
<GridView
android:id="@+id/popGridView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadingEdge="none"
android:focusable="false"
android:gravity="center"
android:horizontalSpacing="@dimen/image_padding"
android:listSelector="@drawable/shape_more_pop"
android:numColumns="5"
android:verticalSpacing="@dimen/image_padding" />
</LinearLayout>
实现
SingleFragmentActivity所有以fragment为布局的activity继承该类:
public abstract class SingleFragmentActivity extends AppCompatActivity {
protected abstract Fragment createFragment();
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取FragmentManager对象
FragmentManager fm = getSupportFragmentManager();
// 获取fragment
Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);
// fragment事务
if (fragment == null) {
fragment = createFragment();
fm.beginTransaction().add(R.id.fragmentContainer, fragment)
.commit();
}
}
}
MainActivity主界面activity
public class MainActivity extends SingleFragmentActivity {
private Toolbar mToolbar;
private MorePopWindow mMorePopWindow;
private Fragment mFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initViews();
}
protected void initViews() {
mToolbar = (Toolbar) findViewById(R.id.toolbar);
mToolbar.setVisibility(View.VISIBLE);
setSupportActionBar(mToolbar);
mMorePopWindow = new MorePopWindow(this);
mMorePopWindow
.setOnMorePopWindowItemClickListener(new OnMorePopWindowItemClickListener() {
@Override
public void onItemClick(int position, String item) {
if (mFragment instanceof FragmentMain) {
((FragmentMain) mFragment)
.changeOtherBeautyType(item);
}
}
});
mToolbar.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_more:
showMorePopWindow();
break;
default:
break;
}
return false;
}
});
}
protected void showMorePopWindow() {
// 显示窗口
mMorePopWindow.showAsDropDown(mToolbar);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_more) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected Fragment createFragment() {
mFragment = new FragmentMain();
return mFragment;
}
}
FragmentMain显示图片列表fragment
public class FragmentMain extends Fragment {
private static final String TAG = "FragmentMain";
/** 从第几项开始加载图片*/
private int pn;
private String tag2;
private String oldTag2;
private GridView mGridView;
private MyGridViewAdapter mAdapter;
public static List<Image> mListImages = new ArrayList<Image>();
private View view;
private PtrFrameLayout mFrame;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_main, container, false);
initViews();
return view;
}
private void initViews() {
tag2 = CustomUtil.getInstance().getString(VolleyUtil.TAG2);
if (tag2 == null) {
tag2 = VolleyUtil.TAG2_DEFAULT;
CustomUtil.getInstance().save(VolleyUtil.TAG2, tag2);
}
oldTag2 = tag2;
mGridView = (GridView) view.findViewById(R.id.gridView);
mAdapter = new MyGridViewAdapter(getActivity(), mListImages);
mAdapter.setLoadListener(new LoadListener() {
@Override
public void onLoad() {
loadNextPage();
}
});
mGridView.setAdapter(mAdapter);
mFrame = (PtrFrameLayout) view
.findViewById(R.id.rotate_header_grid_view_frame);
StoreHouseHeader header = new StoreHouseHeader(getActivity());
header.setPadding(0, 15, 0, 15);
header.initWithString("Loading...");
mFrame.setDurationToCloseHeader(1500);
mFrame.setHeaderView(header);
mFrame.addPtrUIHandler(header);
mFrame.postDelayed(new Runnable() {
@Override
public void run() {
mFrame.autoRefresh(false);
}
}, 100);
mFrame.setPtrHandler(new PtrHandler() {
@Override
public boolean checkCanDoRefresh(PtrFrameLayout frame,
View content, View header) {
return PtrDefaultHandler.checkContentCanBePulledDown(frame,
content, header);
}
@Override
public void onRefreshBegin(PtrFrameLayout frame) {
mListImages.clear();
pn = 0;
if (!oldTag2.equals(tag2)) {
CustomUtil.getInstance().save(VolleyUtil.TAG2, tag2);
oldTag2 = tag2;
}
loadItems();
}
});
}
/**
* 加载一页数据
*/
private void loadItems() {
JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET,
VolleyUtil.getInstance().getUrl(pn, tag2), null,
new Listener<JSONObject>() {
@Override
public void onResponse(JSONObject jsonObject) {
VolleyUtil.parseItems(mListImages, jsonObject);
mFrame.refreshComplete();
updateAdapter();
mAdapter.setFooterViewStatus(FooterView.MORE);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError arg0) {
Toast.makeText(getActivity(), R.string.loading_error,
Toast.LENGTH_SHORT).show();
mFrame.refreshComplete();
}
});
VolleyUtil.getInstance().addToRequestQueue(request);
}
/**
* 加载下一页数据
*/
private void loadNextPage() {
if (mAdapter != null) {
mAdapter.setFooterViewStatus(FooterView.LOADING);
}
pn += VolleyUtil.PAGE_SIZE;
loadItems();
}
private void updateAdapter() {
mAdapter.notifyDataSetChanged();
}
public void changeOtherBeautyType(String tag2) {
this.tag2 = tag2;
mFrame.autoRefresh();
}
}
MorePopWindow显示美女类型
public class MorePopWindow extends PopupWindow {
private View mMoreView;
private LayoutInflater mInflater;
private Context mContext;
private GridView mGridView;
private ArrayAdapter<String> mAdapter;
private List<String> mList;
private String[] mBeautyTypes;
private PopupWindow mPopupWindow;
private int mCurrentPosition = 0;
public MorePopWindow(Context context) {
super(context);
this.mContext = context;
mPopupWindow = this;
mInflater = LayoutInflater.from(mContext);
mMoreView = mInflater.inflate(R.layout.view_more_pop, null);
mGridView = (GridView) mMoreView.findViewById(R.id.popGridView);
initStrings();
mAdapter = new ArrayAdapter<String>(mContext,
R.layout.view_more_pop_item, mBeautyTypes);
mGridView.setAdapter(mAdapter);
mGridView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
if (mOnMorePopWindowItemClickListener != null) {
mOnMorePopWindowItemClickListener.onItemClick(position,
mBeautyTypes[position]);
}
parent.getChildAt(mCurrentPosition).setBackgroundColor(
Color.TRANSPARENT);
view.setBackgroundResource(R.drawable.shape_more_pop);
mCurrentPosition = position;
mPopupWindow.dismiss();
Log.d("More", "onItemClick:" + position);
}
});
// 设置SelectPicPopupWindow的View
this.setContentView(mMoreView);
// 设置SelectPicPopupWindow弹出窗体的宽
this.setWidth(LayoutParams.MATCH_PARENT);
// 设置SelectPicPopupWindow弹出窗体的高
this.setHeight(LayoutParams.WRAP_CONTENT);
// 设置SelectPicPopupWindow弹出窗体可点击
this.setFocusable(true);
// 设置SelectPicPopupWindow弹出窗体动画效果
this.setAnimationStyle(R.style.popwin_anim_style);
// 实例化一个ColorDrawable颜色为透明
ColorDrawable dw = new ColorDrawable(0x55000000);
// 设置SelectPicPopupWindow弹出窗体的背景
this.setBackgroundDrawable(dw);
}
private void initStrings() {
mBeautyTypes = mContext.getResources().getStringArray(
R.array.beauty_array);
mList = new ArrayList<String>();
for (int i = 0; i < 20; i++) {
mList.add("item" + i);
}
}
public interface OnMorePopWindowItemClickListener {
public void onItemClick(int position, String item);
}
private OnMorePopWindowItemClickListener mOnMorePopWindowItemClickListener;
public void setOnMorePopWindowItemClickListener(
OnMorePopWindowItemClickListener onMorePopWindowItemClickListener) {
mOnMorePopWindowItemClickListener = onMorePopWindowItemClickListener;
}
}
MyGridViewAdapter这个是核心,基本上所有的操作都和他有关
public class MyGridViewAdapter extends BaseAdapter {
/** 图片视图类型 */
public static final int VIEW_TYPE_ITEM = 0;
/** 底部图片类型 */
public static final int VIEW_TYPE_FOOT = 1;
/** 视图数量 */
public static final int VIEW_TYPE_COUNT = 2;
private List<Image> mListImages;
private Context mContext;
private LayoutInflater mInflater;
private FooterView mFooterView;
public MyGridViewAdapter(Context context, List<Image> images) {
this.mContext = context;
this.mListImages = images;
mInflater = LayoutInflater.from(mContext);
}
@Override
public int getCount() {
// 注意此处因为加了底部视图所以加1
return mListImages.size() + 1;
}
@Override
public Image getItem(int position) {
return mListImages.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
/**
* 复写该方法使GridView正确缓存不同视图
*/
@Override
public int getViewTypeCount() {
return VIEW_TYPE_COUNT;
}
/**
* 复写该方法使GridView正确缓存不同视图
*/
@Override
public int getItemViewType(int position) {
if (position == getCount() - 1) {
return VIEW_TYPE_FOOT;
}
return VIEW_TYPE_ITEM;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = initConvertView(position, convertView, parent);
}
setDataToConvertView(position, convertView);
return convertView;
}
/**
* 给convertView设置数据
*
* @param position
* @param convertView
*/
private void setDataToConvertView(int position, View convertView) {
if (getItemViewType(position) == VIEW_TYPE_ITEM) {
ViewHolder holder = (ViewHolder) convertView.getTag();
Log.d("MyGrid", "position:" + position);
holder.mImageView
.setTag(R.id.imageView, getItem(position).getUrl());
ImageLoaderUtil.displayImage(getItem(position).getUrl(), holder.mImageView);
} else if (getItemViewType(position) == VIEW_TYPE_FOOT && position != 0) {
setFooterViewStatus(FooterView.MORE);
}
}
/**
* 初始化convertView
*
* @param position
* @param convertView
* @param parent
* @return
*/
private View initConvertView(final int position, View convertView,
ViewGroup parent) {
ViewHolder holder = null;
if (getItemViewType(position) == VIEW_TYPE_FOOT) {// 初始化底部加载更多视图
mFooterView = new FooterView(mContext);
convertView = mFooterView;
mFooterView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mFooterView.getStatus() == FooterView.MORE
&& mLoadListener != null) {
mLoadListener.onLoad();
}
}
});
GridView.LayoutParams pl = new GridView.LayoutParams(
MeasureUtil.getScreenSize(mContext)[0],
LayoutParams.WRAP_CONTENT);
mFooterView.setLayoutParams(pl);
setFooterViewStatus(FooterView.HIDE);
} else {// 初始化图片视图
convertView = mInflater.inflate(R.layout.view_gallery_item, parent,
false);
holder = new ViewHolder();
holder.mImageView = (ImageView) convertView
.findViewById(R.id.imageView);
holder.mImageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Intent intent = new Intent(mContext,
BeautyPagerActivity.class);
intent.putExtra(BeautyPagerActivity.POSITION,
(Integer) v.getTag(R.id.gridView));
mContext.startActivity(intent);
}
}, 200);
}
});
convertView.setTag(holder);
}
return convertView;
}
class ViewHolder {
ImageView mImageView;
}
/**
* 加载更多回调接口
*/
public interface LoadListener {
void onLoad();
}
private LoadListener mLoadListener;
public void setLoadListener(LoadListener loadListener) {
mLoadListener = loadListener;
}
public void setFooterViewStatus(int status) {
if (mFooterView != null) {
mFooterView.setStatus(status);
}
}
}
上一篇博客中我讲到了Adapter中多布局缓存的问题,这次这里也用到了详细请移至Android学习之当ScrollView遭遇ListView(GridView)
好吧接下来我们梳理一遍流程
1.进入主界面也就是当FragmentMain初始化时调用loadItems()方法加载第一页数据并通过volley下载解析放到mListImages中;其实这个过程是通过PtrFrameLayout.autoRefresh()在回调接口中onRefreshBegin()实现刷新过程;
2.刷新MyGridViewAdapter显示图片,通过universal-image-loader的displayImage方法直接将url和imageview传入即可显示图片;也算是相当的简单了;(ps:可是坑才刚刚开始)
3.我们可以点击popwindow中的美女类型类切换图片,因为popwindow在activity中,要改变fragment的值;fragment只能向外公布接口了changeOtherBeautyType方法用来改变图片类型并在此方法中做刷新操作
运行项目检验成果
OMG的图片滑动明显混乱不堪,popwindow说好的从Toolbar下方滑动显示呢?这都是什么呢?
遭受打击之后,博主孕育出了第二篇Android学习之优化美女图片浏览器,让我们一同来优化吧;相信这个过程是痛并快乐着的,哈哈...欲知详情且听下回分解