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

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

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

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

3.可以对书签进行拖拽

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

5.对书签的增删改查

UI如下

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

大致功能的类图如下


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

1.大致启动流程:

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


?
1
mUiController.bookmarksOrHistoryPicker(ComboViews.Bookmarks);



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



?
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);
}



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



?
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);
   }



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


他的指示器是一个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;
         }


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


原文地址:http://my.oschina.net/sfshine/blog/209723
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值