Android利用HorizontalScrollView仿ViewPager設(shè)計簡單相冊
最近學(xué)習(xí)了一個視頻公開課,講到了利用HorizontalScrollView仿ViewPager設(shè)計的一個簡單相冊,其實(shí)主要用了ViewPager緩存的思想。此篇文章參考:Android自定義HorizontalScrollView打造超強(qiáng)Gallery效果(這篇文章與公開課的講的大致一樣)
這里簡單說一下ViewPager的緩存機(jī)制
1.進(jìn)入ViewPager時,加載當(dāng)前頁和后一頁;
2.當(dāng)滑動ViewPager至下一頁時,加載后一頁,此時第一頁是不會銷毀的,同時加載當(dāng)前頁的下一頁。
其實(shí)就是默認(rèn)加載3頁,當(dāng)前頁,前一頁和后一頁。
而此HorizontalScrollView是默認(rèn)加載兩頁的,這個要注意,不然調(diào)度代碼會讓人暈。
話不多說,上代碼:
代碼結(jié)構(gòu)如下圖:

一個View,一個Adapter,一個MainActivity,相信不用解釋,大家也相當(dāng)清楚了,典型的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;// 當(dāng)前的最后一張的index
private int mdisplayLastIndex;// 當(dāng)前顯示的最后一張的index
private int mAllFirstIndex;// 當(dāng)前的第一張index
private GalleryHorizontalScrollViewAdapter mAdapter;// 數(shù)據(jù)適配器
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;
}
/**
* 圖片滾動時回調(diào)接口
*/
public interface OnCurrentImageChangeListener {
void onCurrentImgChanged(int position, View view);
}
/**
* 點(diǎn)擊圖片時回調(diào)接口
*/
public interface OnClickImageChangeListener {
void onClickImageChangeListener(int position, View view);
}
public GalleryHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
// 獲取屏幕寬度
mScreenWidth = getResources().getDisplayMetrics().widthPixels;
}
/**
* 初始化數(shù)據(jù),設(shè)置適配器
*/
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);*/
/**
* 上面注釋掉的是一位老師的寫法,但我查了好多資料,用參數(shù)0和View.MeasureSpec.UNSPECIFIED是一種不太優(yōu)美的做法;
* 好的做法應(yīng)該是
* 當(dāng)View為match_parent時,無法測量出View的大?。ㄈ斡駝偞笊裰v的,確實(shí)是這么一回事,這個具體的原因要結(jié)合源碼分析,可以看一下任大神的博客)
* 當(dāng)View寬高為具體的數(shù)值時,比如100px:
* int w =View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
* int h =View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
* view.measure(w, h);
* 當(dāng)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);
// 待完善的點(diǎn)擊事件
view.setOnClickListener(this);
mContainer.addView(view);
mViewPos.put(view, i);
mAllLastIndex = i;
}
// 初始化并刷新界面
if (null != mOnCurrentImageChangeListener) {
notifyCurrentImgChanged();
}
}
private void notifyCurrentImgChanged() {
// 先清除所有的背景顏色,點(diǎn)擊時設(shè)置為藍(lán)色
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() {// 數(shù)組邊界值計算
if (mAllLastIndex == mAdapter.getCount() - 1) {
return;
}
// 移除第一張圖片,且將水平滾動位置置0
scrollTo(0, 0);
mViewPos.remove(mContainer.getChildAt(0));
mContainer.removeViewAt(0);
// 獲取下一張圖片,并且設(shè)置onclick事件,且加入容器中
View view = mAdapter.getView(++mAllLastIndex, null, mContainer);
view.setOnClickListener(this);
mContainer.addView(view);
mViewPos.put(view, mAllLastIndex);
// 當(dāng)前第一張圖片小標(biāo)
mAllFirstIndex++;
// 如果設(shè)置了滾動監(jiān)聽則觸發(fā)
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);
}
}
至些,調(diào)試運(yùn)行,讀者會發(fā)現(xiàn),整個相冊會非??ǎ?/p>

甚至有的圖片還沒有顯示出來如img_4,看一下logcat,相信大家會發(fā)現(xiàn)原因:

信息已經(jīng)提示的很清楚了,圖片太大,
此時大家應(yīng)該明白了,筆者故意選擇了幾張很大的圖片加載,雖然沒大到直接讓應(yīng)用崩掉,但是體驗(yàn)性已經(jīng)變得非常差了,這是因?yàn)檎n堂上的老師講課時用的圖片都是幾十K的小圖片,加載當(dāng)然不會有問題,所以要想使這個相冊作為一個實(shí)用的相冊,還要處理圖片過大的問題,不然,依舊會造成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圖有點(diǎn)卡,大家可以運(yùn)行看效果):

源碼下載:HorizontalScrollView仿ViewPager設(shè)計相冊
以上就是本文的全部內(nèi)容,希望對大家學(xué)習(xí)Android軟件編程有所幫助。
- Android自定義HorizontalScrollView實(shí)現(xiàn)qq側(cè)滑菜單
- Android HorizontalScrollView左右滑動效果
- Android UI系列-----ScrollView和HorizontalScrollView的詳解
- Android HorizontalScrollView內(nèi)子控件橫向拖拽實(shí)例代碼
- Android自定義HorizontalScrollView打造超強(qiáng)Gallery效果
- Android中HorizontalScrollView使用方法詳解
- Android使用自定義控件HorizontalScrollView打造史上最簡單的側(cè)滑菜單
- Android中實(shí)現(xiàn)多行、水平滾動的分頁的Gridview實(shí)例源碼
- android listview 水平滾動和垂直滾動的小例子
- HorizontalScrollView水平滾動控件使用方法詳解
相關(guān)文章
Android實(shí)現(xiàn)多參數(shù)文件和數(shù)據(jù)上傳
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)多參數(shù)文件和數(shù)據(jù)上傳,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-12-12
淺析Android企業(yè)級開發(fā)數(shù)據(jù)綁定技術(shù)
這篇文章通過代碼實(shí)例分析了Android企業(yè)級開發(fā)數(shù)據(jù)綁定技術(shù)的應(yīng)用以及相關(guān)的原理知識,跟著小編一起學(xué)習(xí)參考下吧。2017-12-12
Android獲取LinearLayout的寬度和高度示例代碼
這篇文章主要介紹了android獲取LinearLayout的寬度和高度,如果想直接獲取在布局文件中定義的組件的寬度和高度,可以直接使用View.getLayoutParams().width和View.getLayoutParams().height,本文結(jié)合示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08
Android使用PullToRefresh完成ListView下拉刷新和左滑刪除功能
ListView下刷新刷功能相信從事Android開發(fā)的猿友們并不陌生,本文就帶領(lǐng)一些剛?cè)腴Tandroid的朋友或者一起愛分享的朋友來簡單的實(shí)現(xiàn)ListView的下拉刷新和左滑刪除效果。感興趣的朋友一起看看吧2016-11-11
利用DrawerLayout和觸摸事件分發(fā)實(shí)現(xiàn)抽屜側(cè)滑效果
這篇文章主要為大家詳細(xì)介紹了利用DrawerLayout和觸摸事件分發(fā)實(shí)現(xiàn)抽屜側(cè)滑效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10
Android ListView 子控件onClick正確獲取position的方法
這篇文章主要介紹了Android ListView 子控件onClick正確獲取position的方法,非常不錯,具有參考借鑒價值,需要的的朋友參考下吧2017-01-01
如何給Flutter界面切換實(shí)現(xiàn)點(diǎn)特效
這篇文章主要給大家介紹了關(guān)于如何給Flutter界面切換實(shí)現(xiàn)點(diǎn)特效的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Flutter具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09

