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

作者:SunShanai 时间:2022-07-29 12:28:33 

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

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

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

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

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

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

话不多说,上代码:

代码结构如下图:

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

一个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);
 }
}

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

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

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

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

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

此时大家应该明白了,笔者故意选择了几张很大的图片加载,虽然没大到直接让应用崩掉,但是体验性已经变得非常差了,这是因为课堂上的老师讲课时用的图片都是几十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图有点卡,大家可以运行看效果):

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

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

标签:Android,HorizontalScrollView,ViewPager,相册
0
投稿

猜你喜欢

  • android Gallery组件实现的iPhone图片滑动效果实例

    2022-09-27 06:38:17
  • C++普通函数指针与成员函数指针实例解析

    2022-09-29 10:19:36
  • 利用Java+Selenium+OpenCV模拟实现网页滑动验证

    2021-07-23 02:45:18
  • java读取xml配置参数代码实例

    2023-11-25 03:03:17
  • C# 10个常用特性汇总

    2023-03-22 01:04:13
  • Spring JDBC的使用方法详解

    2021-12-08 14:05:42
  • VC++时钟函数

    2021-06-17 10:07:51
  • Java实现将图片上传到webapp路径下 路径获取方式

    2023-07-10 12:44:13
  • Java中单例模式的7种写法

    2021-09-05 23:40:57
  • Java httpClient连接池支持多线程高并发的实现

    2022-09-14 04:13:15
  • 浅谈JAVA 内存流的实现

    2021-06-28 05:43:59
  • Java中将File转化为MultipartFile的操作

    2021-07-05 21:25:32
  • volatile与happens-before的关系与内存一致性错误

    2021-12-13 20:25:37
  • android自定义View圆圈拖动

    2023-09-14 01:02:55
  • Java后端Cookie实现(时间戳)代码实例

    2022-05-17 09:39:52
  • C#编程中常见数据结构的比较(Unity3D游戏开发)

    2022-01-02 06:58:03
  • Android实现背景颜色滑动渐变效果的全过程

    2021-08-28 09:23:51
  • C#中Lambda表达式的三种写法

    2022-03-27 02:50:27
  • Java数据结构学习之树

    2022-01-19 23:40:58
  • Idea2020.2创建JavaWeb项目(部署Tomcat)方法详解

    2023-11-02 13:29:52
  • asp之家 软件编程 m.aspxhome.com