Android利用HorizontalScrollView仿ViewPager設(shè)計(jì)簡單相冊
最近學(xué)習(xí)了一個(gè)視頻公開課,講到了利用HorizontalScrollView仿ViewPager設(shè)計(jì)的一個(gè)簡單相冊,其實(shí)主要用了ViewPager緩存的思想。此篇文章參考:Android自定義HorizontalScrollView打造超強(qiáng)Gallery效果(這篇文章與公開課的講的大致一樣)
這里簡單說一下ViewPager的緩存機(jī)制
1.進(jìn)入ViewPager時(shí),加載當(dāng)前頁和后一頁;
2.當(dāng)滑動(dòng)ViewPager至下一頁時(shí),加載后一頁,此時(shí)第一頁是不會(huì)銷毀的,同時(shí)加載當(dāng)前頁的下一頁。
其實(shí)就是默認(rèn)加載3頁,當(dāng)前頁,前一頁和后一頁。
而此HorizontalScrollView是默認(rèn)加載兩頁的,這個(gè)要注意,不然調(diào)度代碼會(huì)讓人暈。
話不多說,上代碼:
代碼結(jié)構(gòu)如下圖:
一個(gè)View,一個(gè)Adapter,一個(gè)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; } /** * 圖片滾動(dòng)時(shí)回調(diào)接口 */ public interface OnCurrentImageChangeListener { void onCurrentImgChanged(int position, View view); } /** * 點(diǎn)擊圖片時(shí)回調(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時(shí),無法測量出View的大?。ㄈ斡駝偞笊裰v的,確實(shí)是這么一回事,這個(gè)具體的原因要結(jié)合源碼分析,可以看一下任大神的博客) * 當(dāng)View寬高為具體的數(shù)值時(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時(shí): * 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(); // 計(jì)算每次加載多少個(gè)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í)設(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ù)組邊界值計(jì)算 if (mAllLastIndex == mAdapter.getCount() - 1) { return; } // 移除第一張圖片,且將水平滾動(dòng)位置置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è)置了滾動(dòng)監(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放在第一個(gè)位置 View view = mAdapter.getView(index, null, mContainer); mViewPos.put(view, index); mContainer.addView(view, 0); view.setOnClickListener(this); // 水平滾動(dòng)位置向左移動(dòng)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)行,讀者會(huì)發(fā)現(xiàn),整個(gè)相冊會(huì)非??ǎ?/p>
甚至有的圖片還沒有顯示出來如img_4,看一下logcat,相信大家會(huì)發(fā)現(xiàn)原因:
信息已經(jīng)提示的很清楚了,圖片太大,
此時(shí)大家應(yīng)該明白了,筆者故意選擇了幾張很大的圖片加載,雖然沒大到直接讓應(yīng)用崩掉,但是體驗(yàn)性已經(jīng)變得非常差了,這是因?yàn)檎n堂上的老師講課時(shí)用的圖片都是幾十K的小圖片,加載當(dāng)然不會(huì)有問題,所以要想使這個(gè)相冊作為一個(gè)實(shí)用的相冊,還要處理圖片過大的問題,不然,依舊會(huì)造成OOM。
此時(shí)就用到這個(gè)工具類了:
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; } }
添加了這個(gè)工具類,上面幾個(gè)類的代碼也要略微修改一下,具體怎么改,大家可以下載下面我上傳的源碼:
至于效果如下動(dòng)圖所示(生成的gif圖有點(diǎn)卡,大家可以運(yùn)行看效果):
源碼下載:HorizontalScrollView仿ViewPager設(shè)計(jì)相冊
以上就是本文的全部內(nèi)容,希望對大家學(xué)習(xí)Android軟件編程有所幫助。
- Android自定義HorizontalScrollView實(shí)現(xiàn)qq側(cè)滑菜單
- Android HorizontalScrollView左右滑動(dòng)效果
- Android UI系列-----ScrollView和HorizontalScrollView的詳解
- Android HorizontalScrollView內(nèi)子控件橫向拖拽實(shí)例代碼
- Android自定義HorizontalScrollView打造超強(qiáng)Gallery效果
- Android中HorizontalScrollView使用方法詳解
- Android使用自定義控件HorizontalScrollView打造史上最簡單的側(cè)滑菜單
- Android中實(shí)現(xiàn)多行、水平滾動(dòng)的分頁的Gridview實(shí)例源碼
- android listview 水平滾動(dòng)和垂直滾動(dòng)的小例子
- HorizontalScrollView水平滾動(dòng)控件使用方法詳解
相關(guān)文章
flutter實(shí)現(xiàn)掃碼槍獲取數(shù)據(jù)源禁止系統(tǒng)鍵盤彈窗示例詳解
這篇文章主要為大家介紹了flutter實(shí)現(xiàn)掃碼槍獲取數(shù)據(jù)源禁止系統(tǒng)鍵盤彈窗示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01Android使用PullToRefresh實(shí)現(xiàn)上拉加載和下拉刷新效果的代碼
這篇文章主要介紹了Android使用PullToRefresh實(shí)現(xiàn)上拉加載和下拉刷新效果 的相關(guān)資料,需要的朋友可以參考下2016-07-07Android指紋識別API講解,一種更快更好的用戶體驗(yàn)
今天小編就為大家分享一篇關(guān)于Android指紋識別API講解,一種更快更好的用戶體驗(yàn),小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-10-10android使用gesturedetector手勢識別示例分享
這篇文章主要介紹了android使用手勢識別的方法,介紹了單擊觸摸屏觸發(fā)的事件和雙擊事件的使用等方法,大家參考使用吧2014-01-01Android?使用maven?publish插件發(fā)布產(chǎn)物(aar)流程實(shí)踐
這篇文章主要介紹了Android?使用maven?publish插件發(fā)布產(chǎn)物(aar)流程實(shí)踐,Android?Gradle插件根據(jù)項(xiàng)目gradle中應(yīng)用不同的插件類型在編譯組裝后會(huì)生成不同的產(chǎn)物,具體相關(guān)介紹,需要的小伙伴可以參考一下2022-09-09學(xué)習(xí)使用Material Design控件(四)Android實(shí)現(xiàn)標(biāo)題欄自動(dòng)縮放、放大效果
這篇文章主要為大家介紹了學(xué)習(xí)使用Material Design控件的詳細(xì)教程,Android實(shí)現(xiàn)標(biāo)題欄自動(dòng)縮放、放大效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07Android應(yīng)用開發(fā)中CardView的初步使用指南
這篇文章主要介紹了Android應(yīng)用開發(fā)中CardView的初步使用指南,CardView主要處理一些卡片型的視圖布局,需要的朋友可以參考下2016-02-02