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

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

就酱~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值