浏览器的书签界面功能还是比较丰富的, 主要有
1.可以按照列表和grid两种方式展示
2.同步后会显示不同用户的书签
3.可以对书签进行拖拽
4.类似文件夹的树形层次, 类型window explorer 的地址管理
5.对书签的增删改查
UI如下
书签模块整体的时序图如下:
大致功能的类图如下
设计图有了, 就开始一步步分析把
1.大致启动流程:
书签的入口还是比较多的, 我们这里只拿从多窗口列表进入书签为例来介绍.从代码可以看到, 在点击了书签按钮之后调用了
1
|
mUiController.bookmarksOrHistoryPicker(ComboViews.Bookmarks);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/**
* 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);
}
|
1
2
3
4
5
6
7
8
9
10
11
|
@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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@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我们后面介绍, 想观察是如何填入数据的:
1
2
3
|
// 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中把获取的数据填入:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
/*异步加载完成后就返回一个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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
/**
* 添加账户后添加该用户的书签
* @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中会计算 一行可以容纳多少个书签:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/**
* 根据整个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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
/*填充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的:
1
2
3
4
5
6
7
8
|
@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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/*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;
}
|
这样就把各个账户的书签展示给用户了!