這是一篇被逼出來的文章。
一入SDK深似海,從此jar包是路人,沒錯,你以為我願意不用ViewPager和Fragment啊,因為SDK為了減少包體大小不能用v4的包啊!坑爹的v4包居然有1M多,你們可真能寫啊。我相信一定有朋友會建議說,把v4包裡相關的類摳出來用啊,呵呵哒,祝你摳的愉快。
言歸正傳,ViewPager和Fragment那是一套相當龐大的界面架構,想要自己實作一個功能相似且能完美的控制記憶體和界面生命周期,短期單人幾乎是不可能完成的任務,我們隻能退而求其次,把底層複雜的邏輯都剝離,再保證沒有記憶體洩漏的情況下,實作界面上看起來相似的功能。大概分析一下滑動界面的需求,抽象出來看就是有N個寬度和螢幕寬度(或者window寬度)一樣的界面排排坐,當使用者滑動的時候不是緩緩過度到下一個頁面,而是有一個彈性效果直接到達下一個頁面,每一個頁面的容器就是Fragment,而N個頁面的容器,就是ViewPager,ViewPager的容器就是我們的Activity。
了解了這個,我們就可以考慮用其他容器來代替ViewPager和Fragment了,橫向滑動的第一選擇當然是HorizontalScrollView,而Fragment和PageAdapter隻能我們自己來實作了,本質就是個View。
直接上代碼,先自定義一個HorizontalScrollView來實作ViewPager的功能:
/**
*
* @author Amuro
*
*/
public class ScrollViewPager extends HorizontalScrollView
{
public interface OnPageChangedListener
{
void onChange(int index);
}
private OnPageChangedListener listener;
public void setOnPageChangedListener(OnPageChangedListener listener)
{
this.listener = listener;
}
private void notifyPageChanged()
{
if (lastPage != currentPage)
{
lastPage = currentPage;
if (listener != null)
{
listener.onChange(currentPage);
}
}
}
private int subChildCount = ;
private int downX = ;
private int lastPage = ;
private int currentPage = ;
private ArrayList<Integer> pointList = new ArrayList<Integer>();
public ScrollViewPager(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
init();
}
public ScrollViewPager(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
public ScrollViewPager(Context context)
{
super(context);
init();
}
private GestureDetector mGestureDetector;
private void init()
{
setHorizontalScrollBarEnabled(false);
mGestureDetector = new GestureDetector(getContext(), new HScrollDetector());
}
// Return false if we're scrolling in the y direction
class HScrollDetector extends SimpleOnGestureListener
{
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY)
{
if (Math.abs(distanceX) > Math.abs(distanceY))
{
return true;
}
return false;
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
switch (ev.getAction())
{
case MotionEvent.ACTION_DOWN:
downX = (int) ev.getX();
break;
}
return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev)
{
switch (ev.getAction())
{
// case MotionEvent.ACTION_DOWN:
// downX = (int) ev.getX();
// break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
{
if (Math.abs((ev.getX() - downX)) > getWidth() / )
{
if (ev.getX() - downX > )
{
smoothScrollToPrePage();
}
else
{
smoothScrollToNextPage();
}
notifyPageChanged();
}
else
{
smoothScrollToCurrent();
}
return true;
}
}
return super.onTouchEvent(ev);
}
private void smoothScrollToCurrent()
{
smoothScrollTo(pointList.get(currentPage), );
}
private void smoothScrollToNextPage()
{
if (currentPage < subChildCount - )
{
currentPage++;
smoothScrollTo(pointList.get(currentPage), );
}
}
private void smoothScrollToPrePage()
{
if (currentPage > )
{
currentPage--;
smoothScrollTo(pointList.get(currentPage), );
}
}
public boolean gotoPage(int page)
{
if (page > && page < subChildCount - )
{
smoothScrollTo(pointList.get(page), );
currentPage = page;
notifyPageChanged();
return true;
}
return false;
}
public void setAdapter(PagerAdapter<?> adapter)
{
LinearLayout container = (LinearLayout) this.getChildAt();
adapter.setContainer(container);
adapter.notifyDatasetChanged();
// receiveChildInfo();
subChildCount = adapter.getCount();
for (int i = ; i < subChildCount; i++)
{
pointList.add( + Constants.HOME_VIEW_WIDTH * i);
}
}
}
核心代碼在onTouchEvent方法裡,熟悉安卓觸摸事件并了解下拉重新整理原理的童鞋應該一看就懂的代碼,就不贅述了,無非就是判斷使用者手勢在橫向上的滑動距離,當超過一定距離就認為使用者是主動滑動到下一頁,通過scoller幫助使用者來實作這個滑動的彈性效果。
這裡我們用到了GestureDetector這個類,目的是為了解決滑動沖突的問題,後面我會再單獨安排文章講這個事兒,這裡先不表。
有了“ViewPager”,下面就是Fragment了,我們先定義一個接口:
public interface IViewController
{
View getView();
void create();
void onShow();
}
其實Fragment的本質就是一個View控制器,為了簡單,這裡就寫幾個主要的回調了。然後Fragment和ViewPager直接的黏合劑PageAdapter我們也仿照着寫一個
public abstract class PagerAdapter<T>
{
protected List<T> pages;
protected ViewGroup container;
public PagerAdapter(List<T> pages)
{
this.pages = pages;
}
protected void setContainer(ViewGroup container)
{
this.container = container;
}
public abstract int getCount();
public abstract void notifyDatasetChanged();
protected abstract T instantiateItem(int positioin);
}
其中container就是我們設定的父容器,一般情況下都是ViewPager,第二個方法大家一看就知道不用多說,第三個方法是給子類初始化具體的fragment來用的,好,針對我們的ViewController,我們來擴充這個類:
package cn.cmgame2_0.launch_model.shortcut.main;
import java.util.List;
import cn.cmgame2_0.launch_model.shortcut.main.V.base.IViewController;
import cn.cmgame2_0.utils.custom_view.PagerAdapter;
public class HomeViewPageAdapter extends PagerAdapter<IViewController>
{
public HomeViewPageAdapter(List<IViewController> pages)
{
super(pages);
}
@Override
public int getCount()
{
return pages.size();
}
@Override
protected IViewController instantiateItem(int positioin)
{
IViewController vc = pages.get(positioin);
container.addView(vc.getView());
return vc;
}
@Override
public void notifyDatasetChanged()
{
container.removeAllViews();
for(int i = ; i < getCount(); i++)
{
instantiateItem(i);
}
}
}
這裡再回去看一下上面自定義HorizontalScrollView的setAdapter方法,其實就把ViewPager中的根布局作為container傳給Adapter,然後adapter中會把設定好的ViewController所有view添加到container中。
好,容器和擴充卡都有了,下面我們根據我們的界面需求去添加具體的ViewController就行了,貼一個例子:
public class RecommendViewController extends ShortcutViewController implements RecommendV
{
private RecommendPresenter presenter;
private long lastRefreshTime = ;
public RecommendViewController(Context context)
{
super(context);
}
private GridView gridViewInstalled;
private InstalledAdapter installedAdapter;
private GridView gridViewRecommend;
private RecommendAdapter recommendAdapter;
private Button buttonRefresh;
private ProgressDialog progressDialog;
@Override
public void create()
{
presenter = new RecommendPresenter(this);
rootView = new RecommendView(context);
initView();
}
private void initView()
{
gridViewInstalled = (GridView)findViewById(RecommendView.id_gv_installed);
gridViewRecommend = (GridView)findViewById(RecommendView.id_gv_recommend);
buttonRefresh = (Button)findViewById(RecommendView.id_bt_refresh);
progressDialog = DialogUtils.getProgressDialog(context);
installedAdapter = new InstalledAdapter(context, presenter.getInstalledGames());
gridViewInstalled.setAdapter(installedAdapter);
gridViewInstalled.setOnItemClickListener(new OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id)
{
}
});
recommendAdapter = new RecommendAdapter(context, presenter.getRecommendGames());
gridViewRecommend.setAdapter(recommendAdapter);
buttonRefresh.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
}
});
}
@Override
public void onShow()
{
}
@Override
public void showLoading()
{
progressDialog.show();
}
@Override
public void hideLoading()
{
progressDialog.dismiss();
}
@Override
public void onError(String errorCode, String errorMsg)
{
ToastUtils.showToast(context, "");
}
@Override
public void onDataFetched(List<RecommendBean> data)
{
recommendAdapter.notifyDataSetChanged();
}
@Override
public Context getContext()
{
return context;
}
}
删除了所有涉及公司業務的代碼,不過大概架構各位也能看懂了,因為不能用xml來建構界面,這裡還需要建構一套替代的架構,其實就是把所有View建構的代碼拉出去獨立成一個體系就好啦,很簡單,不再贅述。眼尖的童鞋應該還看到了代碼裡的V和Presenter,沒錯,這裡還嘗試使用了最新的MVP架構來解耦界面控制與資料操作,後面再開新文章講。
最後在Activity裡把這些元素組織到一起就大功告成了:
private void initViewPager()
{
if(bean.collectionList == null || bean.collectionList.size() == )
{
pageCount = ;
}
else
{
pageCount = bean.collectionList.size() + ;
}
final List<IViewController> vcList = new ArrayList<IViewController>();
for(int i = ; i < pageCount; i++)
{
IViewController vc = null;
if(i == )
{
vc = new RecommendViewController(this);
vc.create();
}
else
{
vc = new CollectionViewController(this, i - );
vc.create();
}
vcList.add(vc);
}
HomeViewPageAdapter adapter = new HomeViewPageAdapter(vcList);
viewPager.setAdapter(adapter);
viewPager.setOnPageChangedListener(new OnPageChangedListener()
{
@Override
public void onChange(int index)
{
indicatorManager.change(index);
vcList.get(index).onShow();
}
});
}
好的api就是要讓使用者用起來和他最熟悉的一模一樣,這也是每個寫架構的童鞋要給自己最起碼的要求。最後貼兩張效果圖:
再安利一下我大移動的咪咕遊戲開放平台:
http://g.10086.cn/open/
就醬~