gecko/mobile/android/base/home/HomePager.java

305 lines
9.7 KiB
Java

/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.home;
import org.mozilla.gecko.R;
import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.ViewHelper;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.ViewGroup.LayoutParams;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.View;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
public class HomePager extends ViewPager {
// Subpage fragment tag
public static final String SUBPAGE_TAG = "home_pager_subpage";
private final Context mContext;
private volatile boolean mLoaded;
private Decor mDecor;
// List of pages in order.
public enum Page {
HISTORY,
BOOKMARKS,
READING_LIST
}
// This is mostly used by UI tests to easily fetch
// specific list views at runtime.
static final String LIST_TAG_HISTORY = "history";
static final String LIST_TAG_BOOKMARKS = "bookmarks";
static final String LIST_TAG_READING_LIST = "reading_list";
static final String LIST_TAG_MOST_VISITED = "most_visited";
static final String LIST_TAG_MOST_RECENT = "most_recent";
static final String LIST_TAG_LAST_TABS = "last_tabs";
private EnumMap<Page, Fragment> mPages = new EnumMap<Page, Fragment>(Page.class);
public interface OnUrlOpenListener {
public enum Flags {
ALLOW_SWITCH_TO_TAB
}
public void onUrlOpen(String url, EnumSet<Flags> flags);
}
public interface OnNewTabsListener {
public void onNewTabs(String[] urls);
}
interface OnTitleClickListener {
public void onTitleClicked(int index);
}
/**
* Special type of child views that could be added as pager decorations by default.
*/
interface Decor {
public void onAddPagerView(String title);
public void removeAllPagerViews();
public void onPageSelected(int position);
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
public void setOnTitleClickListener(OnTitleClickListener onTitleClickListener);
}
static final String CAN_LOAD_ARG = "canLoad";
public HomePager(Context context) {
this(context, null);
}
public HomePager(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
// This is to keep all 3 pages in memory after they are
// selected in the pager.
setOffscreenPageLimit(2);
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (child instanceof Decor) {
((ViewPager.LayoutParams) params).isDecor = true;
mDecor = (Decor) child;
setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
mDecor.onPageSelected(position);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
mDecor.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
@Override
public void onPageScrollStateChanged(int state) { }
});
}
super.addView(child, index, params);
}
/**
* Loads and initializes the pager.
*
* @param fm FragmentManager for the adapter
*/
public void show(FragmentManager fm, Page page, PropertyAnimator animator) {
mLoaded = true;
final TabsAdapter adapter = new TabsAdapter(fm);
// Only animate on post-HC devices, when a non-null animator is given
final boolean shouldAnimate = (animator != null && Build.VERSION.SDK_INT >= 11);
// Add the pages to the adapter in order.
adapter.addTab(Page.HISTORY, HistoryPage.class, new Bundle(),
getContext().getString(R.string.home_history_title));
adapter.addTab(Page.BOOKMARKS, BookmarksPage.class, new Bundle(),
getContext().getString(R.string.bookmarks_title));
adapter.addTab(Page.READING_LIST, ReadingListPage.class, new Bundle(),
getContext().getString(R.string.reading_list_title));
adapter.setCanLoadHint(!shouldAnimate);
setAdapter(adapter);
setCurrentItem(adapter.getItemPosition(page), false);
setVisibility(VISIBLE);
if (shouldAnimate) {
animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
@Override
public void onPropertyAnimationStart() {
setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
@Override
public void onPropertyAnimationEnd() {
setLayerType(View.LAYER_TYPE_NONE, null);
adapter.setCanLoadHint(true);
}
});
ViewHelper.setAlpha(this, 0.0f);
animator.attach(this,
PropertyAnimator.Property.ALPHA,
1.0f);
}
}
/**
* Hides the pager and removes all child fragments.
*/
public void hide() {
mLoaded = false;
setVisibility(GONE);
setAdapter(null);
}
/**
* Determines whether the pager is visible.
*
* Unlike getVisibility(), this method does not need to be called on the UI
* thread.
*
* @return Whether the pager and its fragments are being displayed
*/
public boolean isVisible() {
return mLoaded;
}
class TabsAdapter extends FragmentStatePagerAdapter
implements OnTitleClickListener {
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
final class TabInfo {
private final Page page;
private final Class<?> clss;
private final Bundle args;
private final String title;
TabInfo(Page page, Class<?> clss, Bundle args, String title) {
this.page = page;
this.clss = clss;
this.args = args;
this.title = title;
}
}
public TabsAdapter(FragmentManager fm) {
super(fm);
if (mDecor != null) {
mDecor.removeAllPagerViews();
mDecor.setOnTitleClickListener(this);
}
}
public void addTab(Page page, Class<?> clss, Bundle args, String title) {
TabInfo info = new TabInfo(page, clss, args, title);
mTabs.add(info);
notifyDataSetChanged();
if (mDecor != null) {
mDecor.onAddPagerView(title);
}
}
@Override
public void onTitleClicked(int index) {
setCurrentItem(index, true);
}
public int getItemPosition(Page page) {
for (int i = 0; i < mTabs.size(); i++) {
TabInfo info = mTabs.get(i);
if (info.page == page) {
return i;
}
}
return -1;
}
@Override
public int getCount() {
return mTabs.size();
}
@Override
public Fragment getItem(int position) {
TabInfo info = mTabs.get(position);
return Fragment.instantiate(mContext, info.clss.getName(), info.args);
}
@Override
public CharSequence getPageTitle(int position) {
TabInfo info = mTabs.get(position);
return info.title.toUpperCase();
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
mPages.put(mTabs.get(position).page, fragment);
return fragment;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
mPages.remove(mTabs.get(position).page);
}
public void setCanLoadHint(boolean canLoadHint) {
// Update fragment arguments for future instances
for (TabInfo info : mTabs) {
info.args.putBoolean(CAN_LOAD_ARG, canLoadHint);
}
// Enable/disable loading on all existing pages
for (Fragment page : mPages.values()) {
final HomeFragment homePage = (HomeFragment) page;
homePage.setCanLoadHint(canLoadHint);
}
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
// XXX: Drop the soft keyboard by stealing focus. Note that the HomePager (via XML
// attr) is focusable after its descendants allowing requestFocus to succeed and drop
// the soft keyboard even if there are no other focusable views on the screen (e.g.
// the Reading List is empty).
requestFocus();
}
return super.onInterceptTouchEvent(event);
}
}