編輯:關於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 ArrayListpointList = 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(Listdata) { 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 ListvcList = 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/
就醬~
Android中登錄界面的記住密碼功能實現,將用戶輸入的賬號和密碼以SharedPreferences方式存儲(注意的是,密碼要用MD5明文加密)。 界面xml
現在有這麼一個需求:開啟一個Service服務,獲取當前位置的經緯度數據,將獲取的數據以廣播的方式發送出去,注冊廣播的Activity接收廣播信息,並將接收到的數據在當前
Android 中自定義軟鍵盤 圖一為搜狗輸入法、圖二為自定義密碼鍵盤、圖三為自定義密碼鍵盤 java源文件 package
一、UI 概述Android應用程序的用戶界面是一切,用戶可以看到並與之交互。UI 是用戶能看見並可交互的組件。– 系統 UI– 自定義 UI&n