Android Browser學習八 書籤歷史模塊: 歷史UI的實現

相比與書籤模塊, 歷史模塊功能比較簡單, 代碼很好理解,很值得學習其架構實現, 咱們就從代碼層面來說解其實現, 但願其中的一些東西對未來的開發有幫助. java

首先是onCreate:  android


@Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        setHasOptionsMenu(true); // 屏蔽Activity的menu菜單

        Bundle args = getArguments();
        mDisableNewWindow = args.getBoolean(BrowserBookmarksPage.EXTRA_DISABLE_WINDOW, false);
        int mvlimit = getResources().getInteger(R.integer.most_visits_limit);
        mMostVisitsLimit = Integer.toString(mvlimit);
        mCallback = (CombinedBookmarksCallbacks) getActivity();//奇葩的時候此次是用強轉拿到引用了!
    }



onCreateView : 在平板和手機上效果不同, 他們仍是使用Loader來載入數據: 架構

@Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        mRoot = inflater.inflate(R.layout.history, container, false);
        mAdapter = new HistoryAdapter(getActivity());
        ViewStub stub = (ViewStub) mRoot.findViewById(R.id.pref_stub);
        if (stub != null) {//在sw600dp(平板) 和  手機上UI展現是不一樣的, 其實我的不喜歡這麼實現,不如相似browser的 PhoneUI和TabletUI兩個類來進行
            inflateTwoPane(stub);
        } else {
            inflateSinglePane();
        }

        // Start the loaders
        getLoaderManager().restartLoader(LOADER_HISTORY, null, this);
        getLoaderManager().restartLoader(LOADER_MOST_VISITED, null, this);

        return mRoot;
    }




history.的layout佈局很簡單



<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
>

    <ExpandableListView
        android:id="@+id/history"
        android:layout_height="match_parent"
        android:layout_width="match_parent" />

    <TextView android:id="@android:id/empty"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="@string/empty_history"
        android:visibility="gone"
    />

</FrameLayout>


Loader這裏只看Hisory 訪問最多和這個是同樣的 loader建立ok後會執行:onCreateLoader建立loader: app

@Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        Uri.Builder combinedBuilder = Combined.CONTENT_URI.buildUpon();

        switch (id) {
            case LOADER_HISTORY: {
                String sort = Combined.DATE_LAST_VISITED + " DESC";
                String where = Combined.VISITS + " > 0";
                CursorLoader loader = new CursorLoader(getActivity(), combinedBuilder.build(),
                        HistoryQuery.PROJECTION, where, null, sort);
                return loader;
            }



loader拿到數據後回調onLoadFinished 那麼adapter 就能夠拿到data的cursor 組裝adapter了:
@Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        switch (loader.getId()) {
            case LOADER_HISTORY: {
                mAdapter.changeCursor(data);
                if (!mAdapter.isEmpty() && mGroupList != null
                        && mGroupList.getCheckedItemPosition() == ListView.INVALID_POSITION) {
                    selectGroup(0);
                }

                checkIfEmpty();
                break;
            }



仍是檢查是否顯示emptyview
void checkIfEmpty() {
        if (mAdapter.mMostVisited != null && mAdapter.mHistoryCursor != null) {
            // Both cursors have loaded - check to see if we have data
            if (mAdapter.isEmpty()) {
                mRoot.findViewById(R.id.history).setVisibility(View.GONE);
                mRoot.findViewById(android.R.id.empty).setVisibility(View.VISIBLE);
            } else {
                mRoot.findViewById(R.id.history).setVisibility(View.VISIBLE);
                mRoot.findViewById(android.R.id.empty).setVisibility(View.GONE);
            }
        }
    }



最後吧adapter貼出來 基本這個UI(Phone的)就搭建完成了:
private class HistoryAdapter extends DateSortedExpandableListAdapter {

        private Cursor mMostVisited, mHistoryCursor;
        Drawable mFaviconBackground;//icon的背景

        HistoryAdapter(Context context) {
            super(context, HistoryQuery.INDEX_DATE_LAST_VISITED);
            mFaviconBackground = BookmarkUtils.createListFaviconBackground(context);
        }

        @Override
        public void changeCursor(Cursor cursor) {
            mHistoryCursor = cursor;
            super.changeCursor(cursor);
        }

        void changeMostVisitedCursor(Cursor cursor) {
            if (mMostVisited == cursor) {
                return;
            }
            if (mMostVisited != null) {
                mMostVisited.unregisterDataSetObserver(mDataSetObserver);
                mMostVisited.close();
            }
            mMostVisited = cursor;
            if (mMostVisited != null) {
                mMostVisited.registerDataSetObserver(mDataSetObserver);
            }
            notifyDataSetChanged();
        }

        @Override
        public long getChildId(int groupPosition, int childPosition) {
            if (moveCursorToChildPosition(groupPosition, childPosition)) {
                Cursor cursor = getCursor(groupPosition);
                return cursor.getLong(HistoryQuery.INDEX_ID);
            }
            return 0;
        }

        @Override
        public int getGroupCount() {
            return super.getGroupCount() + (!isMostVisitedEmpty() ? 1 : 0);
        }

        @Override
        public int getChildrenCount(int groupPosition) {
            if (groupPosition >= super.getGroupCount()) {
                if (isMostVisitedEmpty()) {
                    return 0;
                }
                return mMostVisited.getCount();
            }
            return super.getChildrenCount(groupPosition);
        }

        @Override
        public boolean isEmpty() {
            if (!super.isEmpty()) {
                return false;
            }
            return isMostVisitedEmpty();
        }

        private boolean isMostVisitedEmpty() {
            return mMostVisited == null
                    || mMostVisited.isClosed()
                    || mMostVisited.getCount() == 0;
        }

        Cursor getCursor(int groupPosition) {
            if (groupPosition >= super.getGroupCount()) {
                return mMostVisited;
            }
            return mHistoryCursor;
        }

        @Override
        public View getGroupView(int groupPosition, boolean isExpanded,
                View convertView, ViewGroup parent) {
            if (groupPosition >= super.getGroupCount()) {
                if (mMostVisited == null || mMostVisited.isClosed()) {
                    throw new IllegalStateException("Data is not valid");
                }
                TextView item;
                if (null == convertView || !(convertView instanceof TextView)) {
                    LayoutInflater factory = LayoutInflater.from(getContext());
                    item = (TextView) factory.inflate(R.layout.history_header, null);
                } else {
                    item = (TextView) convertView;
                }
                item.setText(R.string.tab_most_visited);
                return item;
            }
            return super.getGroupView(groupPosition, isExpanded, convertView, parent);
        }

        @Override
        boolean moveCursorToChildPosition(
                int groupPosition, int childPosition) {
            if (groupPosition >= super.getGroupCount()) {
                if (mMostVisited != null && !mMostVisited.isClosed()) {
                    mMostVisited.moveToPosition(childPosition);
                    return true;
                }
                return false;
            }
            return super.moveCursorToChildPosition(groupPosition, childPosition);
        }

        @Override
        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
                View convertView, ViewGroup parent) {
            HistoryItem item;
            if (null == convertView || !(convertView instanceof HistoryItem)) {
                item = new HistoryItem(getContext());
                // Add padding on the left so it will be indented from the
                // arrows on the group views.
                item.setPadding(item.getPaddingLeft() + 10,
                        item.getPaddingTop(),
                        item.getPaddingRight(),
                        item.getPaddingBottom());
                item.setFaviconBackground(mFaviconBackground);
            } else {
                item = (HistoryItem) convertView;
            }

            // Bail early if the Cursor is closed.
            if (!moveCursorToChildPosition(groupPosition, childPosition)) {
                return item;
            }

            Cursor cursor = getCursor(groupPosition);
            item.setName(cursor.getString(HistoryQuery.INDEX_TITE));
            String url = cursor.getString(HistoryQuery.INDEX_URL);
            item.setUrl(url);
            byte[] data = cursor.getBlob(HistoryQuery.INDEX_FAVICON);
            if (data != null) {
                item.setFavicon(BitmapFactory.decodeByteArray(data, 0,
                        data.length));
            }
            item.setIsBookmark(cursor.getInt(HistoryQuery.INDEX_IS_BOOKMARK) == 1);
            return item;
        }
    }



這裏還有一個好玩的地方是HistoryItem他繼承自 BookmarkItem, 而後添加了那個星星. BookmarkItem是一個horizontalScrollview, 並attach一個linearLayout (history_item.xml):
BookmarkItem(Context context) {
        super(context);

        setClickable(false);
        setEnableScrolling(false);
        LayoutInflater factory = LayoutInflater.from(context);
        factory.inflate(R.layout.history_item, this);
        mTextView = (TextView) findViewById(R.id.title);
        mUrlText = (TextView) findViewById(R.id.url);
        mImageView = (ImageView) findViewById(R.id.favicon);
        View star = findViewById(R.id.star);
        star.setVisibility(View.GONE);
    }



在書籤界面編輯書籤也有用到:
@Override
    public void onCreateContextMenu(ContextMenu menu, View v,
            ContextMenuInfo menuInfo) {
        MenuInflater inflater = getActivity().getMenuInflater();
        inflater.inflate(R.menu.snapshots_context, menu);
        // Create the header, re-use BookmarkItem (has the layout we want)
        BookmarkItem header = new BookmarkItem(getActivity());
        header.setEnableScrolling(true);
        AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
        populateBookmarkItem(mAdapter.getItem(info.position), header);
        menu.setHeaderView(header);
    }



如圖: ide


寫這個app的人確實很隨意,可是感受寫的很行雲流水, 各類複用都挺駕輕就熟的, 可見人家的功底:


離線功能和歷史功能差很少, 再也不贅述, 只把代碼貼上: 又一次看到Animator , 這個東西確實是很強大啊! 函數


/**
 * 顯示 離線網頁 的fragment
 */
public class BrowserSnapshotPage extends Fragment implements
        LoaderCallbacks<Cursor>, OnItemClickListener {

    public static final String EXTRA_ANIMATE_ID = "animate_id";

    private static final int LOADER_SNAPSHOTS = 1;
    private static final String[] PROJECTION = new String[] {
        Snapshots._ID,
        Snapshots.TITLE,
        "length(" + Snapshots.VIEWSTATE + ")",
        Snapshots.THUMBNAIL,
        Snapshots.FAVICON,
        Snapshots.URL,
        Snapshots.DATE_CREATED,
    };
    private static final int SNAPSHOT_ID = 0;
    private static final int SNAPSHOT_TITLE = 1;
    private static final int SNAPSHOT_VIEWSTATE_LENGTH = 2;
    private static final int SNAPSHOT_THUMBNAIL = 3;
    private static final int SNAPSHOT_FAVICON = 4;
    private static final int SNAPSHOT_URL = 5;
    private static final int SNAPSHOT_DATE_CREATED = 6;

    GridView mGrid;
    View mEmpty;
    SnapshotAdapter mAdapter;
    CombinedBookmarksCallbacks mCallback;
    long mAnimateId;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCallback = (CombinedBookmarksCallbacks) getActivity();//拿到Activity的 Callback
        mAnimateId = getArguments().getLong(EXTRA_ANIMATE_ID); // 外部傳來的須要作動畫item的id
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.snapshots, container, false);
        mEmpty = view.findViewById(android.R.id.empty);
        mGrid = (GridView) view.findViewById(R.id.grid);//很奇怪, xml中明明是SnapshotGridView SnapshotGridView 的做用是設置一行最多的數目, 可是這個彷佛在gridview中能夠實現?
        setupGrid(inflater);
        getLoaderManager().initLoader(LOADER_SNAPSHOTS, null, this);
        return view;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        getLoaderManager().destroyLoader(LOADER_SNAPSHOTS);
        if (mAdapter != null) {
            mAdapter.changeCursor(null);//在銷燬的時候設置cursor爲null
            mAdapter = null;
        }
    }

    void setupGrid(LayoutInflater inflater) {//設置grid
        View item = inflater.inflate(R.layout.snapshot_item, mGrid, false); //item的寬度是wrap_content
        int mspec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        item.measure(mspec, mspec);
        int width = item.getMeasuredWidth();
        mGrid.setColumnWidth(width);//設置 item的寬度
        mGrid.setOnItemClickListener(this);
        mGrid.setOnCreateContextMenuListener(this);
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        if (id == LOADER_SNAPSHOTS) {//建立loader
            return new CursorLoader(getActivity(),
                    Snapshots.CONTENT_URI, PROJECTION,
                    null, null, Snapshots.DATE_CREATED + " DESC");
        }
        return null;
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        if (loader.getId() == LOADER_SNAPSHOTS) {
            if (mAdapter == null) {
                mAdapter = new SnapshotAdapter(getActivity(), data);
                mGrid.setAdapter(mAdapter);
            } else {
                mAdapter.changeCursor(data);
            }
            if (mAnimateId > 0) {
                mAdapter.animateIn(mAnimateId);
                mAnimateId = 0;
                getArguments().remove(EXTRA_ANIMATE_ID);
            }
            boolean empty = mAdapter.isEmpty();
            mGrid.setVisibility(empty ? View.GONE : View.VISIBLE);//若是沒有數據顯示emptyview
            mEmpty.setVisibility(empty ? View.VISIBLE : View.GONE);
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
    }
    //contextMenu的註冊, 顯示刪除的dialog
    @Override
    public void onCreateContextMenu(ContextMenu menu, View v,
            ContextMenuInfo menuInfo) {
        MenuInflater inflater = getActivity().getMenuInflater();
        inflater.inflate(R.menu.snapshots_context, menu);
        // Create the header, re-use BookmarkItem (has the layout we want)
        BookmarkItem header = new BookmarkItem(getActivity());
        header.setEnableScrolling(true);
        AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
        populateBookmarkItem(mAdapter.getItem(info.position), header);
        menu.setHeaderView(header);
    }

    private void populateBookmarkItem(Cursor cursor, BookmarkItem item) {
        item.setName(cursor.getString(SNAPSHOT_TITLE));
        item.setUrl(cursor.getString(SNAPSHOT_URL));
        item.setFavicon(getBitmap(cursor, SNAPSHOT_FAVICON));
    }

    static Bitmap getBitmap(Cursor cursor, int columnIndex) {
        byte[] data = cursor.getBlob(columnIndex);
        if (data == null) {
            return null;
        }
        return BitmapFactory.decodeByteArray(data, 0, data.length);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.delete_context_menu_id) {
            AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
            deleteSnapshot(info.id);
            return true;
        }
        return super.onContextItemSelected(item);
    }

    /*刪除截圖*/
    void deleteSnapshot(long id) {
        final Uri uri = ContentUris.withAppendedId(Snapshots.CONTENT_URI, id);
        final ContentResolver cr = getActivity().getContentResolver();
        new Thread() {
            @Override
            public void run() {
                cr.delete(uri, null, null);
            }
        }.start();

    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position,
            long id) {
        mCallback.openSnapshot(id);
    }

    private static class SnapshotAdapter extends ResourceCursorAdapter {
        private long mAnimateId;
        private AnimatorSet mAnimation;
        private View mAnimationTarget;

        public SnapshotAdapter(Context context, Cursor c) {
            super(context, R.layout.snapshot_item, c, 0);
            mAnimation = new AnimatorSet(); //添加 保存離線網頁動畫 是的縮放動畫 從0 -> 1
            mAnimation.playTogether(
                    ObjectAnimator.ofFloat(null, View.SCALE_X, 0f, 1f),
                    ObjectAnimator.ofFloat(null, View.SCALE_Y, 0f, 1f));
            mAnimation.setStartDelay(100);
            mAnimation.setDuration(400);
            mAnimation.addListener(new AnimatorListener() {

                @Override
                public void onAnimationStart(Animator animation) {
                }

                @Override
                public void onAnimationRepeat(Animator animation) {
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    mAnimateId = 0;
                    mAnimationTarget = null;
                }

                @Override
                public void onAnimationCancel(Animator animation) {
                }
            });
        }

        public void animateIn(long id) {
            mAnimateId = id;
        }

        /*這個函數是在getview的時候調用的 */
        @Override
        public void bindView(View view, Context context, Cursor cursor) {
            long id = cursor.getLong(SNAPSHOT_ID);
            if (id == mAnimateId) {
                if (mAnimationTarget != view) {
                    float scale = 0f;
                    if (mAnimationTarget != null) {
                        scale = mAnimationTarget.getScaleX();
                        mAnimationTarget.setScaleX(1f);
                        mAnimationTarget.setScaleY(1f);
                    }
                    view.setScaleX(scale);
                    view.setScaleY(scale);
                }
                mAnimation.setTarget(view);
                mAnimationTarget = view;
                if (!mAnimation.isRunning()) {
                    mAnimation.start();
                }

            }
            ImageView thumbnail = (ImageView) view.findViewById(R.id.thumb);
            byte[] thumbBlob = cursor.getBlob(SNAPSHOT_THUMBNAIL);
            if (thumbBlob == null) {
                thumbnail.setImageResource(R.drawable.browser_thumbnail);
            } else {
                Bitmap thumbBitmap = BitmapFactory.decodeByteArray(
                        thumbBlob, 0, thumbBlob.length);
                thumbnail.setImageBitmap(thumbBitmap);
            }
            TextView title = (TextView) view.findViewById(R.id.title);
            title.setText(cursor.getString(SNAPSHOT_TITLE));
            TextView size = (TextView) view.findViewById(R.id.size);
            if (size != null) {
                int stateLen = cursor.getInt(SNAPSHOT_VIEWSTATE_LENGTH);
                size.setText(String.format("%.2fMB", stateLen / 1024f / 1024f));
            }
            long timestamp = cursor.getLong(SNAPSHOT_DATE_CREATED);
            TextView date = (TextView) view.findViewById(R.id.date);
            DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT);
            date.setText(dateFormat.format(new Date(timestamp)));
        }

        @Override
        public Cursor getItem(int position) {
            return (Cursor) super.getItem(position);
        }
    }

}
相關文章
相關標籤/搜索