您的位置:首页 > 移动开发 > Android开发

不用ViewPager和Fragment实现滑动页面的效果

2016-11-04 11:10 543 查看
这是一篇被逼出来的文章。

一入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 = 0;
private int downX = 0;
private int lastPage = 0;
private int currentPage = 0;
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() / 4)
{
if (ev.getX() - downX > 0)
{
smoothScrollToPrePage();
}
else
{
smoothScrollToNextPage();
}

notifyPageChanged();
}
else
{
smoothScrollToCurrent();
}
return true;
}
}
return super.onTouchEvent(ev);
}

private void smoothScrollToCurrent()
{
smoothScrollTo(pointList.get(currentPage), 0);
}

private void smoothScrollToNextPage()
{
if (currentPage < subChildCount - 1)
{
currentPage++;
smoothScrollTo(pointList.get(currentPage), 0);
}
}

private void smoothScrollToPrePage()
{
if (currentPage > 0)
{
currentPage--;
smoothScrollTo(pointList.get(currentPage), 0);
}
}

public boolean gotoPage(int page)
{
if (page > 0 && page < subChildCount - 1)
{
smoothScrollTo(pointList.get(page), 0);
currentPage = page;

notifyPageChanged();
return true;
}
return false;
}

public void setAdapter(PagerAdapter<?> adapter)
{
LinearLayout container = (LinearLayout) this.getChildAt(0);
adapter.setContainer(container);
adapter.notifyDatasetChanged();
// receiveChildInfo();
subChildCount = adapter.getCount();
for (int i = 0; i < subChildCount; i++)
{
pointList.add(0 + 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 = 0; 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 = 0;

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() == 0)
{
pageCount = 1;
}
else
{
pageCount = bean.collectionList.size() + 1;
}

final List<IViewController> vcList = new ArrayList<IViewController>();

for(int i = 0; i < pageCount; i++)
{
IViewController vc = null;
if(i == 0)
{
vc = new RecommendViewController(this);
vc.create();
}
else
{
vc = new CollectionViewController(this, i - 1);
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/

就酱~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  sdk android viewpager
相关文章推荐