Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 不用ViewPager和Fragment實現滑動頁面的效果

不用ViewPager和Fragment實現滑動頁面的效果

編輯:關於Android編程

這是一篇被逼出來的文章。

一入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 pointList = new ArrayList();

    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
{
    protected List pages;
    protected ViewGroup container;

    public PagerAdapter(List 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
{
    public HomeViewPageAdapter(List 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 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 vcList = new ArrayList();

        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/

就醬~

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved