2019独角兽企业重金招聘Python工程师标准>>>

浏览器的书签界面功能还是比较丰富的, 主要有

1.可以按照列表和grid两种方式展示

2.同步后会显示不同用户的书签

3.可以对书签进行拖拽

4.类似文件夹的树形层次, 类型window explorer 的地址管理

5.对书签的增删改查

UI如下

书签模块整体的时序图如下:

大致功能的类图如下

设计图有了, 就开始一步步分析把

1.大致启动流程:

书签的入口还是比较多的, 我们这里只拿从多窗口列表进入书签为例来介绍.从代码可以看到, 在点击了书签按钮之后调用了

mUiController.bookmarksOrHistoryPicker(ComboViews.Bookmarks);

这句代码. 正如以前所说, 所有的大功能都是通过controller来实现的 ,然后我们发现, 显示书签的View还是在controller的发起的:

/*** Open the Go page. 打开 历史 书签 窗口* @param startWithHistory If true, open starting on the history tab.*                         Otherwise, start with the bookmarks tab.*/@Overridepublic void bookmarksOrHistoryPicker(ComboViews startView) {if (mTabControl.getCurrentWebView() == null) {return;}// clear action modeif (isInCustomActionMode()) {endActionMode();}Bundle extras = new Bundle();// Disable opening in a new window if we have maxed out the windowsextras.putBoolean(BrowserBookmarksPage.EXTRA_DISABLE_WINDOW,!mTabControl.canCreateNewTab());mUi.showComboView(startView, extras);}

这个函数的作用其实是发起一个activity:

@Overridepublic 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);}

ComboViewActivity是一个actionbar+ viewpager 生成的 的activity, viewpager有三个页面, 分别是书签, 离线网页, 和历史了:

他的指示器是一个actionbar

mViewPager = new ViewPager(this);mViewPager.setId(R.id.tab_view);//可以这样指定一个id给java代码添加的viewsetContentView(mViewPager); //这个view是一个viewpagerfinal 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了:

@Overridepublic 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);//注册contextmenumDragHandler = new BookmarkDragHandler(getActivity(), mDragController,mGrid.getDragAdapter());// Start the loadersLoaderManager lm = getLoaderManager();lm.restartLoader(LOADER_ACCOUNTS, null, this);//把loader传入return mRoot;}

这个Page里面最主要的就是 BookmarkExpandableView, 这个view我们后面介绍, 想观察是如何填入数据的:

// Start the loadersLoaderManager lm = getLoaderManager();lm.restartLoader(LOADER_ACCOUNTS, null, this);//把loader传入

LoaderManger 这是高api版本引进的一个东西, 用于异步加载数据. 具体分析参考 http://blog.csdn.net/zimo2013/article/details/10263339

然后在 onCreateLoader 或返回cursor 来从数据库拿到书签信息 然后 在和 onLoadFinished中把获取的数据填入:

/*异步加载完成后就返回一个cursor我们就可以显示书签等操作了*/@Overridepublic 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 accountNamemGrid.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 loadgetLoaderManager().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 existsint 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 THUMBNAILSadapter.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 getViewmRowCount = rowCount;mRowPadding = rowPadding;mLastViewWidth = viewWidth;if (notify) {notifyDataSetChanged();}}

3.而这个ExpandableView在listview 显示的时候还需要getGroup和getChild:

/*填充childview , 注意每次调用这个函数:1.如果是list 模式 只能添加 一行 2.如果是grid 模式只能添加grid的一行 */@Overridepublic 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);//拿到对应的adapterint rowCount = mRowCount; //列数 / 一行有多少itemif (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 注意,只添加 可以 看到的viewView 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的:

@Overridepublic 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*/@Overridepublic 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;}

这样就把各个账户的书签展示给用户了!

转载于:https://my.oschina.net/sfshine/blog/209723

Android Browser学习七 书签历史模块: 书签UI的实现相关推荐

  1. android browser 书签 路径,Android Browser学习七 书签历史模块: 书签UI的实现(2)

    由于书签模块还是比较复杂的, 为了不让博客变得太长, 故拆分为两篇. 上一篇介绍了书签大致的实现, 本篇主要介绍 1.书签模块BreadCrumb的实现, 2.书签模块与Activity之间的通讯, ...

  2. APICloud原生模块leCast投屏android browser浏览器 tinyPlayer zhikeia的模块的调用方法教程

    lecast.androidBrowser播放器zhikeia 原生tinyPlayer播放器, 一共包含4个模块:lecast.androidBrowser乐播播放器,zhikeia 原生播放器,t ...

  3. Android接入WebView(四)——浏览器书签与历史记录详细处理

    Android接入WebView(一)--基本用法 Android接入WebView(二)--与JavaScript交互 Android接入WebView(三)--浏览器书签与历史记录与二维码分享 A ...

  4. android6.0原生brower_Android Browser学习一 application的初始化

    Android Browser 是一个非常好的学习资料, 使用了自己设计的MVC架构,来管理书签, 浏览器 等各个功能,有具有一定的稳定性,知道我们学习 Browser是从Application开始启 ...

  5. Android内核学习笔记

    0.android系统启动 <Android系统启动流程 -- bootloader> <The Android boot process from power on> < ...

  6. Android优秀学习资源列表

    1Android的发展历史 一.Android系统的发布 Google公司于2007年11月5日宣布与34家手机生产商.运营商以及其他科技公司组成一个开放手机联盟,将共同开发名为"Andro ...

  7. 经验分享:2021最新Android开发者学习路线,深度解析,值得收藏

    前言 前面一篇文章分享了最近整理的大家伙的面试经历总结,有兴趣可以去翻看一下.这位去百度面试的小A同学的面试经历很有趣,因为他拿到了offer但是并没有去,在了解原因后挺认同他的想法的.这种职业价值观 ...

  8. Android SurfaceFlinger学习

    重学系列 1.Android 重学系列 SurfaceFlinger的概述 2.Android 重学系列 Ashmem匿名共享内存 3.Android 重学系列 SurfaceFlinger 的初始化 ...

  9. Android 框架学习4:一次读懂热门图片框架 Picasso 源码及流程

    Android 框架学习1:EventBus 3.0 的特点与如何使用 Android 框架学习2:源码分析 EventBus 3.0 如何实现事件总线 Android 框架学习3:我从 EventB ...

最新文章

  1. java浮点运算很难,java浮点型为什么不能用于严格要求精度的运算
  2. GridView自定义分页
  3. 静态库调用_静态链接和动态链接对比简析
  4. 关系型数据库的核心单元是_核中的数据关系
  5. MySQL-5.7.21非图形化下载、安装、连接问题记录
  6. 周志明:职业电竞选手的Java大神路
  7. mysql表添加完整性约束_SQL语句——完整性约束
  8. 安居客西安房源爬取 + pyecharts 数据展示
  9. stringr | 文本处理方法(Ⅰ-1):字符串处理函数(上)
  10. IDEA 常用快捷键 常用插件 2
  11. day10-函数基础总结
  12. 一起talk C栗子吧(第一百零三回:C语言实例--进程间通信大阅兵)
  13. gaussdb 日常运维命令总结【01】
  14. 西门子PLC如何使Q区输出点在CPU停止后能够常ON或常OFF或实现断电保持?
  15. 荒野今天维护服务器吗,荒野行动1月29日为什么无法登录原因 今天停服更新维护吗?...
  16. 载誉而归!昂视荣膺CAIMRS 2023「自动化创新奖」
  17. matlab实现TE/TM偏振布拉格反射镜
  18. 设计稿自动生成可用页面的展望
  19. XMOS 开发探索0- 新建工程,刷入程序
  20. 【C++】STL简介 -- string 的使用及其模拟实现

热门文章

  1. MyBatis中Like语句使用方式
  2. PHP APM 对比评测:OneAPM, New Relic, 听云
  3. 使用Perl进行网页数据抓取[初学者简明版]
  4. C# 解析 Json数据
  5. jq cookie的使用
  6. gulp排除已压缩文件思路
  7. Language binding(语言绑定)
  8. asp.net MVC:CheckBoxFor 绑定 nullablebool 类型
  9. IntelliJ IDEA的安装详解
  10. [转]linux权限补充:rwt rwT rws rwS 特殊权限