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

(0)

相关推荐

  • Android HorizontalScrollView左右滑动效果

    本文实例为大家分享了Android HorizontalScrollView左右滑动的具体代码,供大家参考,具体内容如下 效果图 一.什么是HorizontalScrollView HorizontalScrollView实际上是一个FrameLayout ,这意味着你只能在它下面放置一个子控件 ,这个子控件可以包含很多数据内容.有可能这个子控件本身就是一个布局控件,可以包含非常多的其他用来展示数据的控件.这个布局控件一般使用的是一个水平布局的LinearLayout.TextView也是一个可

  • Android中HorizontalScrollView使用方法详解

    由于移动设备物理显示空间一般有限,不可能一次性的把所有要显示的内容都显示在屏幕上.所以各大平台一般会提供一些可滚动的视图来向用户展示数据.Android平台框架中为我们提供了诸如ListView.GirdView.ScrollView等滚动视图控件,这几个视图控件也是我们平常使用最多的.下面介绍一下HorizontalScrollView的使用和需要注意的点:  HorizontalScrollView是一个FrameLayout  ,这意味着你只能在它下面放置一个子控件,这个子控件可以包含很多

  • Android UI系列-----ScrollView和HorizontalScrollView的详解

    本篇随笔将讲解一下Android当中比较常用的两个布局容器--ScrollView和HorizontalScrollView,从字面意义上来看也是非常的简单的,ScrollView就是一个可以滚动的View,这个滚动的方向是垂直方向的,而HorizontalScrollView则是一个水平方向的可以滚动的View.本篇随笔可能描述性的知识比较少,最主要还是通过代码来看看如何使用这两个View. 一.ScrollView的简单介绍 首先来看看ScrollView和HorizontalScrollV

  • Android使用自定义控件HorizontalScrollView打造史上最简单的侧滑菜单

    侧滑菜单在很多应用中都会见到,最近QQ5.0侧滑还玩了点花样~~对于侧滑菜单,一般大家都会自定义ViewGroup,然后隐藏菜单栏,当手指滑动时,通过Scroller或者不断的改变leftMargin等实现:多少都有点复杂,完成以后还需要对滑动冲突等进行处理~~今天给大家带来一个简单的实现,史上最简单有点夸张,但是的确是我目前遇到过的最简单的一种实现~~~ 1.原理分析 既然是侧滑,无非就是在巴掌大的屏幕,塞入大概两巴掌大的布局,需要滑动可以出现另一个,既然这样,大家为啥不考虑使用Android

  • Android自定义HorizontalScrollView实现qq侧滑菜单

    今天看了鸿洋_大神在慕课网讲的qq5.0侧滑菜单.学了不少的知识,同时也佩服鸿洋_大神思路的清晰. 看了教程课下也自己实现了一下.代码几乎完全相同  别喷我啊..没办法 o(︶︿︶)o 唉 像素不好 没办法 找不到好的制作gif的软件. 我们暂且称侧滑左边界面的为menu,右边为content 首先是menu的布局 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:androi

  • Android自定义HorizontalScrollView打造超强Gallery效果

    自从Gallery被谷歌废弃以后,Google推荐使用ViewPager和HorizontalScrollView来实现Gallery的效果.的确HorizontalScrollView可以实现Gallery的效果,但是HorizontalScrollView存在一个很大的问题,如果你仅是用来展示少量的图片,应该是没问题的,但是如果我希望HorizontalScrollView可以想ViewPager一样,既可以绑定数据集(动态改变图片),还能做到,不管多少图片都不会OOM(ViewPager内

  • Android HorizontalScrollView内子控件横向拖拽实例代码

    前言 网上ListView上下拖动的例子有,效果也很好,但是项目要横着拖的,只要硬着头皮自己写(主要是没找到合适的),参考文章1修改而来,分享一下. 正文 截图 实现代码: public class HoDragActivity extends Activity { private LinearLayout main; private GestureDetector mGestureDetector; @Override public void onCreate(Bundle savedInst

  • HorizontalScrollView水平滚动控件使用方法详解

    一.简介 用法ScrollView大致相同 二.方法 1)HorizontalScrollView水平滚动控件使用方法 1.在layout布局文件的最外层建立一个HorizontalScrollView控件 2.在HorizontalScrollView控件中加入一个LinearLayout控件,并且把它的orientation设置为horizontal 3.在LinearLayout控件中放入多个装有图片的ImageView控件 2)HorizontalScrollView和ScrollVie

  • android listview 水平滚动和垂直滚动的小例子

    网上有很多解决 android listview 水平和垂直滚动的代码,我没有按照他们说的做(以前没搜到 O(∩_∩)O~) 我采用的是添加HorizontalScrollViewJava代码 复制代码 代码如下: < ScrollView android:id="@+id/ScrollView01" android:layout_height="300px" android:layout_x="16px" android:layout_y

  • Android中实现多行、水平滚动的分页的Gridview实例源码

    功能要求: (1)比如每页显示2X2,总共2XN,每个item显示图片+文字(点击有链接). 如果单行水平滚动,可以用Horizontalscrollview实现. 如果是多行水平滚动,则结合Gridview(一般是垂直滚动的)和Horizontalscrollview实现. (2)水平滚动翻页,下面有显示当前页的icon. 1.实现自定义的HorizontalScrollView(HorizontalScrollView.java): 因为要翻页时需要传当前页给调用者,所以fling函数中自己

随机推荐