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