HorizontalScrollView实现Gallery效果,可滑到最后项且点击可滑动到相应位置

本文主要是介绍HorizontalScrollView实现Gallery效果,可滑到最后项且点击可滑动到相应位置,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

需求

实际开发中,往往需要用到图集展示(Gallery)效果。做过android开发的都知道android有个控件叫Gallery,就是专门用来实现图集展示效果的。使用方法也很简单,一个item布局+一个adapter就可以搞定。

这里写图片描述

然而Google官方由于某些原因决定弃用Gallery控件(具体原因不明,似乎是因为使用Gallery容易造成内存泄露),官方文档中提倡大家使用ViewPager和HorizontalScrollView代替它。在android 5.0后 Google又推出了一个新控件RecyclerView,也可以用来实现Gallery效果,这里不做讨论。

本文主要讲解如何使用HorizontalScrollView实现完美的Gallery效果。

参考鸿神的博客Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果

在上面这篇博客中,主要实现了以下几个功能:

1.自定义HorizontalScrollView实现动态加载、删除图片,避免出现OOM
2.当某一图片滑动到屏幕左边时,将该图片置为当前选中项,并通过回调接口将该图片位置信息传递给界面。
3.实现图片的点击监听,点击图片时通过回调接口将该图片信息传递给界面。

本文主要是在上面的HorizontalScrollView的基础上做一些功能优化和补充,主要有以下两个功能:

1、当点击某一子项时,将HorizontalScrollView自动滑动到该子项位置
2、解决HorizontalScrollView无法滑动到最后一个子项的问题

效果演示图

这里写图片描述

实现&代码

先构建一个Adapter,方便HorizontalScrollView获取子项图片的数据

定制Adapter

public class HorizontalScrollViewAdapter extends BaseAdapter{private LayoutInflater mInflater;private List<Integer> mData;public HorizontalScrollViewAdapter(Context context, List<Integer> mData){mInflater = LayoutInflater.from(context);this.mData = mData;}public int getCount(){return mData.size();}public Object getItem(int position){return mData.get(position);}public long getItemId(int position){return position;}public View getView(int position, View convertView, ViewGroup parent){ViewHolder viewHolder = null;if (convertView == null){viewHolder = new ViewHolder();convertView = mInflater.inflate(R.layout.item_gallery, parent, false);viewHolder.mImg = (ImageView) convertView.findViewById(R.id.item_gallery_iv);convertView.setTag(viewHolder);} else{viewHolder = (ViewHolder) convertView.getTag();}viewHolder.mImg.setImageResource(mData.get(position));return convertView;}private class ViewHolder{ImageView mImg;}}

这里每个子项都只包含了一个ImageView,所以布局文件也很简单。
使用资源文件dimen定义图片的宽高,除了方便修改之外,也方便程序中可以动态获得图片的宽高。

Gallery_item布局

<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/item_gallery_iv"android:layout_width="@dimen/gallery_width"android:layout_height="@dimen/gallery_height">
</ImageView>

把最复杂的放到最后,先来看看用法。

注:这里为了演示效果所以使两个HorizontalScrollView分别实现了滑动监听和点击监听,实际应用中可以同时监听两个接口。

用法

public class MainActivity extends ActionBarActivity {//实现滑动监听的HorizontalScrollViewprivate MyHorizontalScrollView mSlideGallery;//实现点击监听的HorizontalScrollViewprivate MyHorizontalScrollView mClickGallery;//用于展示当前选中的图片private ImageView mImg;//图片资源文件数组private List<Integer> mData = 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, R.drawable.h,R.drawable.i, R.drawable.j, R.drawable.k));@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mImg = (ImageView) findViewById(R.id.id_content);mSlideGallery = (MyHorizontalScrollView) findViewById(R.id.slide_gallery);mClickGallery = (MyHorizontalScrollView) findViewById(R.id.click_gallery);//添加滚动回调mSlideGallery.setCurrentImageChangedListener(new MyHorizontalScrollView.CurrentImageChangedListener() {@Overridepublic void onCurrentImgChanged(int position, View viewIndicator) {mImg.setImageResource(mData.get(position));viewIndicator.setAlpha(1f);}});//初始化,配置adaptermSlideGallery.initData(new HorizontalScrollViewAdapter(this, mData));//添加点击回调mClickGallery.setOnItemClickListener(new MyHorizontalScrollView.OnItemClickListener() {@Overridepublic void onItemClick(View view, int position) {mImg.setImageResource(mData.get(position));}});//初始化,配置adaptermClickGallery.initData(new HorizontalScrollViewAdapter(this, mData));}}

再来看看主界面布局

主界面布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:background="@android:color/white"><FrameLayoutandroid:layout_width="fill_parent"android:layout_height="0dp"android:layout_weight="1"><ImageViewandroid:id="@+id/id_content"android:layout_width="fill_parent"android:layout_height="fill_parent"android:layout_gravity="center"android:layout_margin="10dp"android:scaleType="centerCrop"android:src="@drawable/ic_launcher"/></FrameLayout><com.yetwish.horizatalscrollviewdemo.widget.MyHorizontalScrollViewandroid:id="@+id/slide_gallery"android:layout_marginTop="10dp"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="bottom"android:background="@android:color/white"android:scrollbars="none"><LinearLayoutandroid:layout_width="@dimen/gallery_width"android:layout_height="@dimen/gallery_height"android:layout_gravity="center_vertical"android:orientation="horizontal"></LinearLayout></com.yetwish.horizatalscrollviewdemo.widget.MyHorizontalScrollView><com.yetwish.horizatalscrollviewdemo.widget.MyHorizontalScrollViewandroid:layout_marginTop="10dp"android:id="@+id/click_gallery"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="bottom"android:background="@android:color/white"android:scrollbars="none"><LinearLayoutandroid:layout_width="@dimen/gallery_width"android:layout_height="@dimen/gallery_height"android:layout_gravity="center_vertical"android:orientation="horizontal"></LinearLayout></com.yetwish.horizatalscrollviewdemo.widget.MyHorizontalScrollView>
</LinearLayout>

最后是实现自定义HorizontalScrollView,先来看需要用到的成员变量。

自定义HorizontalScrollView

public class MyHorizontalScrollView extends HorizontalScrollView implementsView.OnClickListener {/*** 屏幕宽度*/private int mScreenWidth;/*** 图片的宽度*/private int mChildWidth;/*** horizontalScrollView 下的 linearLayout*/private LinearLayout mContainer;/*** 每屏最多显示的View的个数*/private int mCountOneScreen;/*** adapter*/private HorizontalScrollViewAdapter mAdapter;/*** 保存View与位置的键值对*/private Map<View, Integer> mViewPos = new HashMap<>();/*** 当前屏幕显示的最后一张图片的下标*/private int mLastIndex;/*** 当前第一张图片的下标*/private int mFirstIndex;/***  当前图片切换 回调接口*/private CurrentImageChangedListener mItemChangedListener;/***  点击图片 回调接口*/private OnItemClickListener mItemClickListener;/*** 可额外添加的空白图片的个数*/private int mAdditionalCount;/*** 标识是否已加载完所有空白图片*/private boolean isLoaded = false;/*** 当前点击选中的图片*/private int mCurrentClickedItem;

其中

    /*** 可额外添加的空白图片的个数*/private int mAdditionalCount;/*** 标识是否已加载完所有空白图片*/private boolean isLoaded = false;

这两个成员变量就是为了解决HorizontalScrollView无法滑动到最后一张图片的问题。

由于我们在主布局中,将HorizontalScrollView的宽度置为”wrap_content”,所以当滑动到最后一屏图片时,即表示HorizontalScrollView已经滑到底,无法再向左滑动,则最后一屏的图片无法滑到屏幕左边。

这时候有一个比较简单的解决方法就是当滑动到最后一屏时,动态地给HorizontalScrollView添加相应个数(对应最后一屏图片的个数)具备相同宽度的空白View,这样就可以将HorizontalScrollView滑动到最后一张图片。当然,如果这时候从底部往前滑动,就需要动态地将空白view清除掉。

再看看MyHorizontalScrollView的构造方法和初始化方法

   public MyHorizontalScrollView(Context context, AttributeSet attrs) {super(context, attrs);// 获取屏幕宽度WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics outMetrics = new DisplayMetrics();wm.getDefaultDisplay().getMetrics(outMetrics);mScreenWidth = outMetrics.widthPixels;}/*** 初始化数据和adapter*/public void initData(HorizontalScrollViewAdapter adapter) {mAdapter = adapter;mContainer = (LinearLayout) getChildAt(0);// 获取适配器的第一个viewView view = mAdapter.getView(0, null, mContainer);mContainer.addView(view);// 计算当前view的宽高if (mChildWidth == 0) {mChildWidth = (int) getResources().getDimension(R.dimen.gallery_width) + 1;// 计算每次加载多少个viewmCountOneScreen = mScreenWidth / mChildWidth + 2;mAdditionalCount = mCountOneScreen-1;// 如果adapter中view 的总数比能一屏能加载的少,则把最多能加载数置为view总数if (mCountOneScreen > mAdapter.getCount())mCountOneScreen = mAdapter.getCount();}// 初始化第一屏幕initFirstScreenChild(mCountOneScreen);}

这里我们通过DisplayMetrics获取屏幕的宽度,再由屏幕宽度/图片的宽度获得每屏最多可显示的图片个数

因为当有mScreenWidth / mChildWidth个图片滑动到屏幕中间部分时,屏幕两边也会显示到另外两张图片的边缘

所以这里需要mCountOneScreen = mScreenWidth / mChildWidth + 2;

另外,我们通过mChildWidth = (int) getResources().getDimension(
R.dimen.gallery_width);
来获取图片的宽度

     /*** 加载第一屏的图片*/public void initFirstScreenChild(int mCountOneScreen) {mContainer = (LinearLayout) getChildAt(0);mContainer.removeAllViews();mViewPos.clear();for (int i = 0; i < mCountOneScreen; i++) {View view = mAdapter.getView(i, null, mContainer);view.setOnClickListener(this);//初始化时 默认选中第一个if (i == 0) view.setAlpha(1f);else view.setAlpha(0.5f);mContainer.addView(view);mViewPos.put(view, i);mLastIndex = i;}if (mItemChangedListener != null) {notifyCurrentItemChanged();}}

回调接口定义

    //滑动切换回调接口public interface CurrentImageChangedListener {void onCurrentImgChanged(int position, View viewIndicator);}//点击切换回调接口public interface OnItemClickListener {void onItemClick(View view, int position);}//设置滑动切换接口public void setCurrentImageChangedListener(CurrentImageChangedListener listener) {mItemChangedListener = listener;}//设置点击切换接口public void setOnItemClickListener(OnItemClickListener listener) {mItemClickListener = listener;}

接下来就是核心部分,通过实现onTouchEvent()实现动态加载上一张图片和加载下一张图片:

  @Overridepublic boolean onTouchEvent(@NonNull MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_MOVE:int scrollX = getScrollX();// 如果当前scrollX 为图片的宽度,加载下一张,if (scrollX >= mChildWidth) {loadNextImage();}// 如果scrollX = 0 ,加载上一张 if (scrollX == 0) {loadPreImage();}break;}return super.onTouchEvent(ev);}

加载下一张图片的同时移除第一张,避免出现OOM

加载到最后一屏之前,每次根据adapter中存放的数据进行加载下一张图片,并更新mFirstIndex和mCurrentIndex

当加载到最后一屏时,即当mFirstInde==mAdapter.getCount() - mCountOneScreen时,判断mAdditionalCount 是否大于0,是则还未滑动到底,则加载空白image并加入到HorizontalScrollView中;否则表示已滑到底,无需添加空白image,则直接return

    private void loadNextImage() {View view;if (isLoaded) {return;}// 后面没有if (mFirstIndex >= mAdapter.getCount() - mCountOneScreen&& mAdditionalCount > 0) {view = new ImageView(getContext());view.setLayoutParams(new ViewGroup.LayoutParams((int) getContext().getResources().getDimension(R.dimen.gallery_width),(int) getContext().getResources().getDimension(R.dimen.gallery_height)));mAdditionalCount--;if (mAdditionalCount == 0)isLoaded = true;} else {// 获取下一张图片view = mAdapter.getView(++mLastIndex, null, mContainer);view.setOnClickListener(this);}// 移除第一张图片,并将水平滚动位置置0scrollTo(0, 0);mViewPos.remove(mContainer.getChildAt(0));mContainer.removeViewAt(0);mContainer.addView(view);mViewPos.put(view, mLastIndex);//如果生成的next view并不是当前选择的view,则将其透明度设为50%,否则设为1if (mLastIndex != mCurrentClickedItem)view.setAlpha(0.5f);else view.setAlpha(1f);// 当后面还有图片时,更新第一张图片的下标if (mFirstIndex < mAdapter.getCount() - 1)mFirstIndex++;//通知item changed 回调if (mItemChangedListener != null) {notifyCurrentItemChanged();}}

加载上一张的同时,移除最后一张,避免出现OOM

当滑动到最后一屏时,因为在loadNextImage()中,添加的是空白的image,并没有更新mLastIndex,所以在loadPreImage()中,当处于最后一屏时,不更新mLastIndex

注:滑动过程中,由于当前点击的图片可能被滑到不可见区域而被remove掉,所以当加载新的图片时,需要判断其是否为当前点击的图片,如果是则将其置为选中状态

    private void loadPreImage() {// 前面没有了if (mFirstIndex == 0) {return;}// 如果当前可添加空白image的值小于初始值,则表示此时正滑到最后一屏,则更新mAdditionalCount,且将isLoaded置为trueelse if (mAdditionalCount < mCountOneScreen-1) {mAdditionalCount++;isLoaded = false;}// 获取当前应该显示为第一张图片的下标int index = mFirstIndex - 1;if (index >= 0) {// 移除最后一张int oldViewPos = mContainer.getChildCount() - 1;mViewPos.remove(mContainer.getChildAt(oldViewPos));mContainer.removeViewAt(oldViewPos);View view = mAdapter.getView(index, null, mContainer);if (index != mCurrentClickedItem)view.setAlpha(0.5f);else view.setAlpha(1f);view.setOnClickListener(this);mContainer.addView(view, 0);mViewPos.put(view, index);// 水平滚动位置向左移动view的宽度个像素scrollTo(mChildWidth, 0);// 当滑动到最后一屏之前,每次load pre 则mCurrentPos --if (mFirstIndex <= mAdapter.getCount() - mCountOneScreen)mLastIndex--;mFirstIndex--;//通知item changed 回调if (mItemChangedListener != null) {notifyCurrentItemChanged();}}}

实现接口回调

实现点击图片时,HorizontalScrollView自动滑动到该图片位置,主要是要计算点击时需要滑动的距离,调用smoothScrollBy(x,y),并更新当前点击项即可

    //滑动切换接口回调private void notifyCurrentItemChanged() {for (int i = 0; i < mContainer.getChildCount(); i++) {mContainer.getChildAt(i).setAlpha(0.5f);}mItemChangedListener.onCurrentImgChanged(mFirstIndex, mContainer.getChildAt(0));}   //点击接口回调@Overridepublic void onClick(View view) {if (mItemClickListener != null) {for (int i = 0; i < mContainer.getChildCount(); i++) {mContainer.getChildAt(i).setAlpha(0.5f);}view.setAlpha(1f);mCurrentClickedItem = mViewPos.get(view);// 点击项与当前显示项之间 图片个数int itemCount = mViewPos.get(view) - mFirstIndex;// 点击时,由于每次只加载了显示在屏幕上的几个图片,所以应先根据itemCount加载出后面的图片,才能scroll,否则会无法滑动for (int i = 0; i < itemCount; i++) {loadNextImage();}smoothScrollBy(calculateScrollWidth(itemCount), 0);mItemClickListener.onItemClick(view, mViewPos.get(view));}}/*** 计算点击时 将点击项滑动到屏幕最左边,需要滑动的距离** @param itemCount , 前后两项 间隔的图片个数* @return 需滑动的距离*/private int calculateScrollWidth(int itemCount) {int scrollWidth;if (itemCount > 2)scrollWidth = mChildWidth * (itemCount - 3);else if (itemCount > 1) {scrollWidth = mChildWidth * (itemCount - 2);} else {scrollWidth = mChildWidth * (itemCount - 1);}return scrollWidth;}

到此就实现了超赞的Gallery效果,大家可以试着写个demo,希望对个位看官有所帮助。有任何问题可评论或者发邮件跟我联系yetwish@gmail.com

在实际项目中,图集展示往往要涉及到图片压缩和图片的异步加载,博主这几天自己写了个ImageLoader,使用线程池管理并发,使用handler、looper实现异步,但感觉还不是很科学,所以这两天先研究研究volley的ImageLoader的实现机制,之后再整理一套异步加载图片的机制出来

这篇关于HorizontalScrollView实现Gallery效果,可滑到最后项且点击可滑动到相应位置的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1065253

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

【测试】输入正确用户名和密码,点击登录没有响应的可能性原因

目录 一、前端问题 1. 界面交互问题 2. 输入数据校验问题 二、网络问题 1. 网络连接中断 2. 代理设置问题 三、后端问题 1. 服务器故障 2. 数据库问题 3. 权限问题: 四、其他问题 1. 缓存问题 2. 第三方服务问题 3. 配置问题 一、前端问题 1. 界面交互问题 登录按钮的点击事件未正确绑定,导致点击后无法触发登录操作。 页面可能存在

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

防近视护眼台灯什么牌子好?五款防近视效果好的护眼台灯推荐

在家里,灯具是属于离不开的家具,每个大大小小的地方都需要的照亮,所以一盏好灯是必不可少的,每个发挥着作用。而护眼台灯就起了一个保护眼睛,预防近视的作用。可以保护我们在学习,阅读的时候提供一个合适的光线环境,保护我们的眼睛。防近视护眼台灯什么牌子好?那我们怎么选择一个优秀的护眼台灯也是很重要,才能起到最大的护眼效果。下面五款防近视效果好的护眼台灯推荐: 一:六个推荐防近视效果好的护眼台灯的

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、