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软件编程有所帮助。