相比与书签模块, 历史模块功能比较简单, 代码很好理解,很值得学习其架构实现, 我们就从代码层面来讲解其实现, 希望其中的一些东西对将来的开发有帮助.
首先是onCreate:
@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;
}
<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:
@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;
}
@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;
}
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);
}
}
}
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;
}
}
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);
}
如图:
离线功能和历史功能差不多, 不再赘述, 只把代码贴上: 又一次看到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);
}
}
}