瀏覽器的書籤界面功能仍是比較豐富的, 主要有 java
1.能夠按照列表和grid兩種方式展現 android
2.同步後會顯示不一樣用戶的書籤 數據庫
3.能夠對書籤進行拖拽 windows
4.相似文件夾的樹形層次, 類型window explorer 的地址管理 api
5.對書籤的增刪改查 瀏覽器
UI以下 less
書籤模塊總體的時序圖以下: 異步
大體功能的類圖以下 ide
設計圖有了, 就開始一步步分析把 函數
1.大體啓動流程:
書籤的入口仍是比較多的, 咱們這裏只拿從多窗口列表進入書籤爲例來介紹.從代碼能夠看到, 在點擊了書籤按鈕以後調用了
mUiController.bookmarksOrHistoryPicker(ComboViews.Bookmarks);
/** * Open the Go page. 打開 歷史 書籤 窗口 * @param startWithHistory If true, open starting on the history tab. * Otherwise, start with the bookmarks tab. */ @Override public void bookmarksOrHistoryPicker(ComboViews startView) { if (mTabControl.getCurrentWebView() == null) { return; } // clear action mode if (isInCustomActionMode()) { endActionMode(); } Bundle extras = new Bundle(); // Disable opening in a new window if we have maxed out the windows extras.putBoolean(BrowserBookmarksPage.EXTRA_DISABLE_WINDOW, !mTabControl.canCreateNewTab()); mUi.showComboView(startView, extras); }
@Override public void showComboView(ComboViews startingView, Bundle extras) { Intent intent = new Intent(mActivity, ComboViewActivity.class); intent.putExtra(ComboViewActivity.EXTRA_INITIAL_VIEW, startingView.name()); intent.putExtra(ComboViewActivity.EXTRA_COMBO_ARGS, extras); Tab t = getActiveTab(); if (t != null) { intent.putExtra(ComboViewActivity.EXTRA_CURRENT_URL, t.getUrl()); } mActivity.startActivityForResult(intent, Controller.COMBO_VIEW); }
他的指示器是一個actionbar
mViewPager = new ViewPager(this); mViewPager.setId(R.id.tab_view);//能夠這樣指定一個id給java代碼添加的view setContentView(mViewPager); //這個view是一個viewpager final ActionBar bar = getActionBar(); bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); if (BrowserActivity.isTablet(this)) {//若是是平面設置一下參數 bar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_USE_LOGO); bar.setHomeButtonEnabled(true); } else { bar.setDisplayOptions(0); } mTabsAdapter = new TabsAdapter(this, mViewPager); //把vierpager 和 actionbar關聯 mTabsAdapter.addTab(bar.newTab().setText(R.string.tab_bookmarks), BrowserBookmarksPage.class, args); //每一個tab由 bar + fragment組成 mTabsAdapter.addTab(bar.newTab().setText(R.string.tab_history), BrowserHistoryPage.class, args); mTabsAdapter.addTab(bar.newTab().setText(R.string.tab_snapshots), BrowserSnapshotPage.class, args);
這裏咱們只看BrowserBookmarksPage: 當這個page顯示的時候首先執行的固然是onCreateView了:
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mRoot = inflater.inflate(R.layout.bookmarks, container, false); mEmptyView = mRoot.findViewById(android.R.id.empty); mGrid = (BookmarkExpandableView) mRoot.findViewById(R.id.grid); mGrid.setOnChildClickListener(this); mGrid.setColumnWidthFromLayout(R.layout.bookmark_thumbnail); mGrid.setBreadcrumbController(this); setEnableContextMenu(mEnableContextMenu);//註冊contextmenu mDragHandler = new BookmarkDragHandler(getActivity(), mDragController, mGrid.getDragAdapter()); // Start the loaders LoaderManager lm = getLoaderManager(); lm.restartLoader(LOADER_ACCOUNTS, null, this);//把loader傳入 return mRoot; }
這個Page裏面最主要的就是 BookmarkExpandableView, 這個view咱們後面介紹, 想觀察是如何填入數據的:
// Start the loaders LoaderManager lm = getLoaderManager(); lm.restartLoader(LOADER_ACCOUNTS, null, this);//把loader傳入
LoaderManger 這是高api版本引進的一個東西, 用於異步加載數據. 具體分析參考 http://blog.csdn.net/zimo2013/article/details/10263339
而後在 onCreateLoader 或返回cursor 來從數據庫拿到書籤信息 而後 在和 onLoadFinished中把獲取的數據填入:
/*異步加載完成後就返回一個cursor咱們就能夠顯示書籤等操做了*/ @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { if (loader.getId() == LOADER_ACCOUNTS) { LoaderManager lm = getLoaderManager(); int id = LOADER_BOOKMARKS; while (cursor.moveToNext()) { String accountName = cursor.getString(0); String accountType = cursor.getString(1); Bundle args = new Bundle(); args.putString(ACCOUNT_NAME, accountName); args.putString(ACCOUNT_TYPE, accountType); BrowserBookmarksAdapter adapter = new BrowserBookmarksAdapter( getActivity(), VIEW_THUMBNAILS); mBookmarkAdapters.put(id, adapter); boolean expand = true; try { expand = mState.getBoolean(accountName != null ? accountName : BookmarkExpandableView.LOCAL_ACCOUNT_NAME); } catch (JSONException e) {} // no state for accountName mGrid.addAccount(accountName, adapter, expand); lm.restartLoader(id, args, this); id++; } // TODO: Figure out what a reload of these means // Currently, a reload is triggered whenever bookmarks change // This is less than ideal // It also causes UI flickering as a new adapter is created // instead of re-using an existing one when the account_name is the // same. // For now, this is a one-shot load getLoaderManager().destroyLoader(LOADER_ACCOUNTS); } else if (loader.getId() >= LOADER_BOOKMARKS) { BrowserBookmarksAdapter adapter = mBookmarkAdapters.get(loader.getId()); adapter.changeCursor(cursor); } }
2. 咱們能夠看到, BookMarkPage最重要的Ui是 BookmarkExpandableView, 他的實現仍是有些小複雜的, 分析一下他的類圖:
原來他就是一個ExpandableListview, 這也和咱們看到的東西是同樣的, 其實這個書籤的顯示有兩種展現風格:
a.相似上面UI的gridview的風格
b.相似小米瀏覽器書籤的listview風格.
有UI上能夠看出, 這些UI的實現都是在ExpandableView的getChildView的返回來實現的:
那麼就從展現開始開始一步步分析吧:
1.Adapter的組裝: 在onLoadfinished函數中: 載入數據ok以後, BookmarkExpandableView會按照不一樣的帳戶信息來添加書籤:
mGrid.addAccount(accountName, adapter, expand);
添加的核心代碼就是添加一個新的adapter
/** * 添加帳戶後添加該用戶的書籤 * @param accountName * @param adapter * @param expandGroup */ public void addAccount(String accountName, BrowserBookmarksAdapter adapter, boolean expandGroup) { // First, check if it already exists int indexOf = mAdapter.mGroups.indexOf(accountName); if (indexOf >= 0) { //存在可是有更新 BrowserBookmarksAdapter existing = mAdapter.mChildren.get(indexOf); if (existing != adapter) { existing.unregisterDataSetObserver(mAdapter.mObserver);//先不要監聽數據變化 // Replace the existing one 替換書籤 mAdapter.mChildren.remove(indexOf); mAdapter.mChildren.add(indexOf, adapter); adapter.registerDataSetObserver(mAdapter.mObserver); //註冊監聽數據的變化 } } else { //沒有添加這個用戶的書籤, if (mCurrentView >= 0) { //mCurrentView 標示 是list仍是grid THUMBNAILS adapter.selectView(mCurrentView); } mAdapter.mGroups.add(accountName);//添加用戶名 mAdapter.mChildren.add(adapter); //添加 adapter.registerDataSetObserver(mAdapter.mObserver); } mAdapter.notifyDataSetChanged(); //添加後展開這個group if (expandGroup) { expandGroup(mAdapter.getGroupCount() - 1); } }
2.咱們知道 在View 從添加到顯示給用戶會走onMeasure onLayout onDraw流程,
在onMeasure中會計算 一行能夠容納多少個書籤:
/** * 根據整個view的寬度來測量 一行能夠容納多少書籤 * @param viewWidth */ public void measureChildren(int viewWidth) { if (mLastViewWidth == viewWidth) return; int rowCount = viewWidth / mColumnWidth; if (mMaxColumnCount > 0) {//不能超過最大的列數 rowCount = Math.min(rowCount, mMaxColumnCount); } int rowPadding = (viewWidth - (rowCount * mColumnWidth)) / 2;//設置 gridview的padding 使其水平居中 boolean notify = rowCount != mRowCount || rowPadding != mRowPadding; //是否發生了變化須要刷新, 會進行刷新view的操做 即 從adapter getView mRowCount = rowCount; mRowPadding = rowPadding; mLastViewWidth = viewWidth; if (notify) { notifyDataSetChanged(); } }
3.而這個ExpandableView在listview 顯示的時候還須要getGroup和getChild:
/*填充childview , 注意每次調用這個函數:1.若是是list 模式 只能添加 一行 2.若是是grid 模式只能添加grid的一行 */ @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate(R.layout.bookmark_grid_row, parent, false); } BrowserBookmarksAdapter childAdapter = mChildren.get(groupPosition);//拿到對應的adapter int rowCount = mRowCount; //列數 / 一行有多少item if (childAdapter.getViewMode() == BrowserBookmarksPage.VIEW_LIST) { rowCount = 1; //若是是list就是一列 } LinearLayout row = (LinearLayout) convertView; if (row.getChildCount() > rowCount) { row.removeViews(rowCount, row.getChildCount() - rowCount);//可能刷新界面 不顯示已經刪除的view } for (int i = 0; i < rowCount; i++) {//一個一個的添加view 注意,只添加 能夠 看到的view View cv = null; if (row.getChildCount() > i) { cv = row.getChildAt(i); } int realChildPosition = (childPosition * rowCount) + i; if (realChildPosition < childAdapter.getCount()) { View v = childAdapter.getView(realChildPosition, cv, row); v.setTag(R.id.group_position, groupPosition);//能夠往tag 中放n個東西 v.setTag(R.id.child_position, realChildPosition); v.setTag(R.id.child_id, childAdapter.getItemId(realChildPosition)); v.setOnClickListener(mChildClickListener); v.setLongClickable(mLongClickable); if (mDragHandler != null) { v.setOnLongClickListener(mChildOnLongClickListener); //註冊拖拽事件監聽 mDragHandler.registerBookmarkDragHandler(v); } if (cv == null) { row.addView(v); } else if (cv != v) { row.removeViewAt(i); row.addView(v, i);//更新新的view } else { cv.setVisibility(View.VISIBLE); } } else if (cv != null) { cv.setVisibility(View.GONE); } } return row; }
也就是說, 每一行的數據是經過BrowserBookmarksAdapter的getView來產生view的:
這個東西繼承了CursorAdapter , 應該是經過bindView來組裝view的:
@Override public void bindView(View view, Context context, Cursor cursor) { if (mCurrentView == BrowserBookmarksPage.VIEW_LIST) { bindListView(view, context, cursor); } else { bindGridView(view, context, cursor); } }
而後是getGroupView:
/*group的 view*/ @Override public View getGroupView(int groupPosition, boolean isExpanded, View view, ViewGroup parent) { if (view == null) { view = mInflater.inflate(R.layout.bookmark_group_view, parent, false); view.setOnClickListener(mGroupOnClickListener); } view.setTag(R.id.group_position, groupPosition); FrameLayout crumbHolder = (FrameLayout) view.findViewById(R.id.crumb_holder); //若是是二級菜單或更多 在gruopview上顯示那個 上層目錄 crumbHolder.removeAllViews(); BreadCrumbView crumbs = getBreadCrumbView(groupPosition); //若是是二級菜單 在gruopview上顯示那個 上層目錄 if (crumbs.getParent() != null) {//之前添加過 目錄導航了 先刪除之前的 ((ViewGroup)crumbs.getParent()).removeView(crumbs); } crumbHolder.addView(crumbs); TextView name = (TextView) view.findViewById(R.id.group_name); String groupName = mGroups.get(groupPosition); if (groupName == null) { groupName = mContext.getString(R.string.local_bookmarks); } name.setText(groupName); return view; }
這樣就把各個帳戶的書籤展現給用戶了!