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

来源:互联网 发布:macd指标公式源码 编辑:程序博客网 时间:2024/06/05 17:21

这是一篇被逼出来的文章。
一入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/

就酱~

0 0
原创粉丝点击