Android利用HorizontalScrollView仿ViewPager设计简单相册

最近学习了一个视频公开课,讲到了利用HorizontalScrollView仿ViewPager设计的一个简单相册,其实主要用了ViewPager缓存的思想。此篇文章参考:Android自定义HorizontalScrollView打造超强Gallery效果(这篇文章与公开课的讲的大致一样)

这里简单说一下ViewPager的缓存机制

1.进入ViewPager时,加载当前页和后一页;

2.当滑动ViewPager至下一页时,加载后一页,此时第一页是不会销毁的,同时加载当前页的下一页。

其实就是默认加载3页,当前页,前一页和后一页。

而此HorizontalScrollView是默认加载两页的,这个要注意,不然调度代码会让人晕。

话不多说,上代码:

代码结构如下图:

一个View,一个Adapter,一个MainActivity,相信不用解释,大家也相当清楚了,典型的MVC模式~

package com.ssa.horizontalscrollview.myview; import java.util.HashMap; import java.util.Map; import com.ssa.horizontalscrollview.myUtils.DisplayUtil; import android.content.Context; import android.graphics.Color; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.widget.HorizontalScrollView; import android.widget.LinearLayout; public class GalleryHorizontalScrollView extends HorizontalScrollView implements OnClickListener { private LinearLayout mContainer;// MyHorizontalScrollView中的LinearLayout private int mChildWidth;// 子元素的宽度 private int mChildHeight;// 子元素的高度 private int mAllLastIndex;// 当前的最后一张的index private int mdisplayLastIndex;// 当前显示的最后一张的index private int mAllFirstIndex;// 当前的第一张index private GalleryHorizontalScrollViewAdapter mAdapter;// 数据适配器 private int mScreenWidth;// 屏幕的宽度 private int mCountOneScreen; private Map<View, Integer> mViewPos = new HashMap<View, Integer>(); private OnCurrentImageChangeListener mOnCurrentImageChangeListener; private OnClickImageChangeListener mOnClickImageChangeListener; public void setmOnCurrentImageChangeListener( OnCurrentImageChangeListener mListener) { this.mOnCurrentImageChangeListener = mListener; } public void setmOnClickImageListener(OnClickImageChangeListener mListener) { this.mOnClickImageChangeListener = mListener; } /** * 图片滚动时回调接口 */ public interface OnCurrentImageChangeListener { void onCurrentImgChanged(int position, View view); } /** * 点击图片时回调接口 */ public interface OnClickImageChangeListener { void onClickImageChangeListener(int position, View view); } public GalleryHorizontalScrollView(Context context, AttributeSet attrs) { super(context, attrs); // 获取屏幕宽度 mScreenWidth = getResources().getDisplayMetrics().widthPixels; } /** * 初始化数据,设置适配器 */ public void initData(GalleryHorizontalScrollViewAdapter mAdapter) { this.mAdapter = mAdapter; mContainer = (LinearLayout) getChildAt(0); final View view = mAdapter.getView(0, null, mContainer); mContainer.addView(view); if (mChildHeight == 0 && mChildWidth == 0) { /*int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);*/ /** * 上面注释掉的是一位老师的写法,但我查了好多资料,用参数0和View.MeasureSpec.UNSPECIFIED是一种不太优美的做法; * 好的做法应该是 * 当View为match_parent时,无法测量出View的大小(任玉刚大神讲的,确实是这么一回事,这个具体的原因要结合源码分析,可以看一下任大神的博客) * 当View宽高为具体的数值时,比如100px: * int w =View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY); * int h =View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY); * view.measure(w, h); * 当View宽高为wrap_content时: * int w =View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST); * int h =View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST); * view.measure(w, h); * * 我的此View高度为固定的150dip,宽度为wrap_content */ int heightPx = DisplayUtil.dip2px(getContext(), 150); int w =View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST); int h =View.MeasureSpec.makeMeasureSpec(heightPx, View.MeasureSpec.EXACTLY); view.measure(w, h); mChildHeight = view.getMeasuredHeight(); mChildWidth = view.getMeasuredWidth(); // 计算每次加载多少个item mdisplayLastIndex = mScreenWidth / mChildWidth; mCountOneScreen = mdisplayLastIndex + 1; initFirstScreenChildren(mdisplayLastIndex + 1); } } /** * 加载第一屏的元素 * * @param mDisplayCountOneScreen */ private void initFirstScreenChildren(int mDisplayCountOneScreen) { mContainer = (LinearLayout) getChildAt(0); mContainer.removeAllViews(); mViewPos.clear(); for (int i = 0; i < mDisplayCountOneScreen; i++) { View view = mAdapter.getView(i, null, mContainer); // 待完善的点击事件 view.setOnClickListener(this); mContainer.addView(view); mViewPos.put(view, i); mAllLastIndex = i; } // 初始化并刷新界面 if (null != mOnCurrentImageChangeListener) { notifyCurrentImgChanged(); } } private void notifyCurrentImgChanged() { // 先清除所有的背景颜色,点击时设置为蓝色 for (int i = 0; i < mContainer.getChildCount(); i++) { mContainer.getChildAt(i).setBackgroundColor(Color.WHITE); } mOnCurrentImageChangeListener.onCurrentImgChanged(mAllFirstIndex, mContainer.getChildAt(0)); } @Override public boolean onTouchEvent(MotionEvent ev) { /* * Log.e("X", getX()+""); Log.e("ChildX", * mContainer.getChildAt(0).getX()+""); Log.e("RawX",getLeft() +""); */ switch (ev.getAction()) { case MotionEvent.ACTION_MOVE: int scrollX = getScrollX(); Log.e("ScrollX", scrollX + ""); if (scrollX >= mChildWidth) { // 加载下一页,移除第一张 loadNextImg(); } if (scrollX == 0) { // 加载上一页,移除最后一张 loadPreImg(); } break; } return super.onTouchEvent(ev); } private void loadNextImg() {// 数组边界值计算 if (mAllLastIndex == mAdapter.getCount() - 1) { return; } // 移除第一张图片,且将水平滚动位置置0 scrollTo(0, 0); mViewPos.remove(mContainer.getChildAt(0)); mContainer.removeViewAt(0); // 获取下一张图片,并且设置onclick事件,且加入容器中 View view = mAdapter.getView(++mAllLastIndex, null, mContainer); view.setOnClickListener(this); mContainer.addView(view); mViewPos.put(view, mAllLastIndex); // 当前第一张图片小标 mAllFirstIndex++; // 如果设置了滚动监听则触发 if (mOnCurrentImageChangeListener != null) { notifyCurrentImgChanged(); } } private void loadPreImg() { if (mAllFirstIndex == 0) { return; } int index = mAllLastIndex - mCountOneScreen; if (index >= 0) { // 移除最后一张 int oldViewPos = mContainer.getChildCount() - 1; mViewPos.remove(mContainer.getChildAt(oldViewPos)); mContainer.removeViewAt(oldViewPos); // 将加入的View放在第一个位置 View view = mAdapter.getView(index, null, mContainer); mViewPos.put(view, index); mContainer.addView(view, 0); view.setOnClickListener(this); // 水平滚动位置向左移动View的宽度的像素 scrollTo(mChildWidth, 0); mAllLastIndex--; mAllFirstIndex--; if (null != mOnCurrentImageChangeListener) { notifyCurrentImgChanged(); } } } @Override public void onClick(View v) { if(null!=mOnClickImageChangeListener){ mOnClickImageChangeListener.onClickImageChangeListener(mViewPos.get(v), v); } } }

下面是Adapter的源码:

package com.ssa.horizontalscrollview.myview; import java.util.List; import com.ssa.horizontalscrollview.R; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; public class GalleryHorizontalScrollViewAdapter { private LayoutInflater mInflater; private List<Integer> mDatas; public GalleryHorizontalScrollViewAdapter(Context context, List<Integer> mDatas) { mInflater = LayoutInflater.from(context); this.mDatas = mDatas; } public Object getItem(int position) { return mDatas.get(position); } public long getItemId(int position) { return position; } public int getCount() { return mDatas.size(); } public View getView(int position, View contentView, ViewGroup parent) { ViewHolder myHolder = null; if (null == contentView) { contentView = mInflater.inflate(R.layout.activity_gallery_item, parent, false); myHolder = new ViewHolder(contentView); contentView.setTag(myHolder); }else { myHolder = (ViewHolder)contentView.getTag(); } myHolder.ivImg.setImageResource(mDatas.get(position)); myHolder.tvText.setText("Img_"+position); return contentView; } private static class ViewHolder { ImageView ivImg; TextView tvText; public ViewHolder(View view) { ivImg = (ImageView)view.findViewById(R.id.iv_content); tvText =(TextView)view.findViewById(R.id.tv_index); } } }

下面是MainActivity的源码:

package com.ssa.horizontalscrollview; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import android.app.Activity; import android.graphics.Color; import android.os.Bundle; import android.view.View; import android.widget.ImageView; import com.ssa.horizontalscrollview.myview.GalleryHorizontalScrollView; import com.ssa.horizontalscrollview.myview.GalleryHorizontalScrollView.OnClickImageChangeListener; import com.ssa.horizontalscrollview.myview.GalleryHorizontalScrollView.OnCurrentImageChangeListener; import com.ssa.horizontalscrollview.myview.GalleryHorizontalScrollViewAdapter; public class MainActivity extends Activity { private GalleryHorizontalScrollView mHorizontalScrollView; private GalleryHorizontalScrollViewAdapter mAdapter; private ImageView mImg; private List<Integer> mDatas = new ArrayList<Integer>(Arrays.asList( R.drawable.a, R.drawable.b, R.drawable.c, R.drawable.d, R.drawable.e,R.drawable.f,R.drawable.g)); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mImg = (ImageView)findViewById(R.id.iv_content); mHorizontalScrollView = (GalleryHorizontalScrollView)findViewById(R.id.mhsv_gallery_container); mAdapter = new GalleryHorizontalScrollViewAdapter(this, mDatas); mHorizontalScrollView.setmOnCurrentImageChangeListener(new OnCurrentImageChangeListener() { @Override public void onCurrentImgChanged(int position, View view) { mImg.setImageResource(mDatas.get(position)); view.setBackgroundColor(Color.parseColor("#6d9eeb")); } }); mHorizontalScrollView.setmOnClickImageListener(new OnClickImageChangeListener() { @Override public void onClickImageChangeListener(int position, View view) { mImg.setImageResource(mDatas.get(position)); } }); mHorizontalScrollView.initData(mAdapter); } }

至些,调试运行,读者会发现,整个相册会非常卡,

甚至有的图片还没有显示出来如img_4,看一下logcat,相信大家会发现原因:

信息已经提示的很清楚了,图片太大,

此时大家应该明白了,笔者故意选择了几张很大的图片加载,虽然没大到直接让应用崩掉,但是体验性已经变得非常差了,这是因为课堂上的老师讲课时用的图片都是几十K的小图片,加载当然不会有问题,所以要想使这个相册作为一个实用的相册,还要处理图片过大的问题,不然,依旧会造成OOM。

此时就用到这个工具类了:

package com.ssa.horizontalscrollview.myUtils; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; public class BitmapUtil { public static Bitmap decodeSampledBitmapFromResources(Resources res, int resId, int reqWidth, int reqHeight) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); options.inSampleSize = calculateInsampleSize(options, reqWidth, reqHeight); options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } public static int calculateInsampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } return inSampleSize; } }

添加了这个工具类,上面几个类的代码也要略微修改一下,具体怎么改,大家可以下载下面我上传的源码:
至于效果如下动图所示(生成的gif图有点卡,大家可以运行看效果):

源码下载:HorizontalScrollView仿ViewPager设计相册

以上就是本文的全部内容,希望对大家学习Android软件编程有所帮助。

时间: 2024-08-04 14:44:18

Android利用HorizontalScrollView仿ViewPager设计简单相册的相关文章

Android利用HorizontalScrollView仿ViewPager设计简单相册_Android

最近学习了一个视频公开课,讲到了利用HorizontalScrollView仿ViewPager设计的一个简单相册,其实主要用了ViewPager缓存的思想.此篇文章参考:Android自定义HorizontalScrollView打造超强Gallery效果(这篇文章与公开课的讲的大致一样)  这里简单说一下ViewPager的缓存机制        1.进入ViewPager时,加载当前页和后一页:        2.当滑动ViewPager至下一页时,加载后一页,此时第一页是不会销毁的,同时

Android利用传感器仿微信摇一摇功能

传感器 简单的介绍一下传感器: 就是设备用来感知周边环境变化的硬件. Android中的传感器包含在传感器框架中,属于android.hardware.*(硬件部分) 传感器框架主要包含四个部分: ① SensorManager:用来获取传感器的入口,它是一个系统的服务,还可以为传感器注册与取消注册监听 ② Sensor: 具体的传感器,包含了传感器的名字,类型,采样率 ③ SensorEvent:传感器事件,包含了传感器采集回来的数据,传感器的精度 ④ SensorEventListener:

Android应用中仿今日头条App制作ViewPager指示器_Android

一.概述顶部ViewPager指示器的字体变色,该效果图是这样的: 大概是今天头条的app,神奇的地方就在于,切换ViewPager页面的时候,顶部指示器改成了字体颜色的变化,个人觉得还是不错的. 那么核心的地方就是做一个支持字体这样逐渐染色就可以了,我大概想了32s,扫描了一些可能实现的方案,最终定位了一个靠谱的,下面我就带大家开始实现的征程. 实现之前贴一下我们的效果图:1.简单使用 效果如上图了,关于颜失色的改变我添加了两个方向,一个是左方向,一个是有方向. 单纯的使用,可能觉得没什么意思

Android利用ViewPager实现滑动广告板实例源码_Android

•android-support-v4.jar,这是谷歌官方给我们提供的一个兼容低版本Android设备的软件包,里面包囊了只有在Android3.0以上可以使用的api.而ViewPager就是其中之一,利用它我们可以做很多事情,从最简单的导航,到页面切换菜单等等. •ViewPager的功能就是可以使视图滑动,就像Lanucher左右滑动那样. •本Demo向大家演示ViewPager的使用,并在用户未滑动View时,每隔5s钟自动切换到下一个View(循环切换),而当用户有Touch到Vi

Android利用ViewPager实现滑动广告板实例源码

•android-support-v4.jar,这是谷歌官方给我们提供的一个兼容低版本Android设备的软件包,里面包囊了只有在Android3.0以上可以使用的api.而ViewPager就是其中之一,利用它我们可以做很多事情,从最简单的导航,到页面切换菜单等等. •ViewPager的功能就是可以使视图滑动,就像Lanucher左右滑动那样. •本Demo向大家演示ViewPager的使用,并在用户未滑动View时,每隔5s钟自动切换到下一个View(循环切换),而当用户有Touch到Vi

求帮助啊 急用啊-利用C语言设计简单的文件加密解密程序

问题描述 利用C语言设计简单的文件加密解密程序 利用C语言设计简单的文件加密解密程序,并通过代码实现,希望得到源代码. 解决方案 最简单的用xor实现加密.http://blog.csdn.net/fdipzone/article/details/20413631 解决方案二: http://blog.163.com/chatter@126/blog/static/12766566120101020102247603/http://blog.csdn.net/szhhck/article/det

Android 利用ViewPager实现图片可以左右循环滑动效果附代码下载_Android

首先给大家展示靓照,对效果图感兴趣的朋友可以继续往下阅读哦. ViewPager这个小demo实现的是可以左右循环滑动图片,下面带索引,滑到最后一页在往右滑动就要第一页,第一页往左滑动就到最后一页,上面是效果图,用美女图片是我一贯的作风,呵呵  1.    首先看一些layout下的xml <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width=&qu

Android 利用ViewPager+GridView实现首页导航栏布局分页效果_Android

最近我尝试使用ViewPager+GridView实现的,看起来一切正常,废话不多说,具体代码如下: 如图是效果图   首先分析下思路 1.首先是怎么布局:整体是一个ViewPager将GridView作为一个View添加到ViewPager的adapter中,下方是圆点 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas

Android利用ViewPager实现用户引导界面效果的方法_Android

本文实例讲述了Android利用ViewPager实现用户引导界面效果.分享给大家供大家参考,具体如下: 我相信有很多朋友在装完软件首次打开时,有很多软件都有一个软件功能介绍, 例如刚装完微信打开它,有很多介绍微信功能的图片,并且在屏幕下方有很多小圆点提示你当前图片的位置. 今天我就来实现这么个功能 所实现的功能: 1.可以左右滑动功能图片. 2.图片的索引 看出当前图片所在的位置. 3.可循环滑动. 4.图片的索引带有动画效果. 本次学习主要是利用ViewPager实现用户引导界面 在这里,我