本文旨在教你打造一个真实的一个图片浏览器,并非写一堆链接用来测试;也算是用到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不是页数;
项目结构及分包:
如果是为了测试其实倒也无所谓,但是这里我还是要做到一定的规范;正所谓写代码很容易,写好却不容易:
包结构图
其他包就不多加解释了;
欢迎界面
欢迎界面的高斯模糊效果请移至高斯模糊制作启动界面
主界面
头部是一个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(); } }
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学习之优化美女图片浏览器,让我们一同来优化吧;相信这个过程是痛并快乐着的,哈哈...欲知详情且听下回分解