diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 3572280a3ad..383b8813f93 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -240,6 +240,7 @@ FENNEC_JAVA_FILES = \ home/SearchLoader.java \ home/SimpleCursorLoader.java \ home/SuggestClient.java \ + home/TabMenuStrip.java \ home/TopBookmarkItemView.java \ home/TopBookmarksAdapter.java \ home/TopBookmarksView.java \ @@ -476,6 +477,7 @@ RES_LAYOUT = \ res/layout/home_history_list.xml \ res/layout/home_most_recent_page.xml \ res/layout/home_most_visited_page.xml \ + res/layout/home_pager.xml \ res/layout/home_reading_list_page.xml \ res/layout/home_search_item_row.xml \ res/layout/home_suggestion_prompt.xml \ @@ -500,6 +502,7 @@ RES_LAYOUT = \ res/layout/remote_tabs_child.xml \ res/layout/remote_tabs_group.xml \ res/layout/search_engine_row.xml \ + res/layout/tab_menu_strip.xml \ res/layout/tabs_panel.xml \ res/layout/tabs_counter.xml \ res/layout/tabs_panel_header.xml \ @@ -521,6 +524,7 @@ RES_LAYOUT = \ RES_LAYOUT_LARGE_V11 = \ res/layout-large-v11/browser_toolbar.xml \ + res/layout-large-v11/home_pager.xml \ $(NULL) RES_LAYOUT_LARGE_LAND_V11 = \ @@ -641,6 +645,7 @@ RES_DRAWABLE_MDPI = \ res/drawable-mdpi/bookmark_folder_closed.png \ res/drawable-mdpi/bookmark_folder_opened.png \ res/drawable-mdpi/desktop_notification.png \ + res/drawable-mdpi/home_tab_menu_strip.9.png \ res/drawable-mdpi/ic_menu_addons_filler.png \ res/drawable-mdpi/ic_menu_bookmark_add.png \ res/drawable-mdpi/ic_menu_bookmark_remove.png \ @@ -749,6 +754,7 @@ RES_DRAWABLE_HDPI = \ res/drawable-hdpi/alert_mic.png \ res/drawable-hdpi/alert_mic_camera.png \ res/drawable-hdpi/arrow_popup_bg.9.png \ + res/drawable-hdpi/home_tab_menu_strip.9.png \ res/drawable-hdpi/ic_menu_addons_filler.png \ res/drawable-hdpi/ic_menu_bookmark_add.png \ res/drawable-hdpi/ic_menu_bookmark_remove.png \ @@ -844,6 +850,7 @@ RES_DRAWABLE_XHDPI = \ res/drawable-xhdpi/alert_mic.png \ res/drawable-xhdpi/alert_mic_camera.png \ res/drawable-xhdpi/arrow_popup_bg.9.png \ + res/drawable-xhdpi/home_tab_menu_strip.9.png \ res/drawable-xhdpi/ic_menu_addons_filler.png \ res/drawable-xhdpi/ic_menu_bookmark_add.png \ res/drawable-xhdpi/ic_menu_bookmark_remove.png \ diff --git a/mobile/android/base/home/HomePager.java b/mobile/android/base/home/HomePager.java index a1cd7ab460e..ad4ab20fba2 100644 --- a/mobile/android/base/home/HomePager.java +++ b/mobile/android/base/home/HomePager.java @@ -14,10 +14,13 @@ 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; @@ -29,6 +32,7 @@ public class HomePager extends ViewPager { private final Context mContext; private volatile boolean mLoaded; + private Decor mDecor; // List of pages in order. public enum Page { @@ -51,6 +55,21 @@ public class HomePager extends ViewPager { 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); + } + public HomePager(Context context) { this(context, null); } @@ -64,6 +83,30 @@ public class HomePager extends ViewPager { 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. * @@ -113,7 +156,8 @@ public class HomePager extends ViewPager { return mLoaded; } - class TabsAdapter extends FragmentStatePagerAdapter { + class TabsAdapter extends FragmentStatePagerAdapter + implements OnTitleClickListener { private final ArrayList mTabs = new ArrayList(); final class TabInfo { @@ -132,12 +176,26 @@ public class HomePager extends ViewPager { 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) { diff --git a/mobile/android/base/home/TabMenuStrip.java b/mobile/android/base/home/TabMenuStrip.java new file mode 100644 index 00000000000..ea81420d738 --- /dev/null +++ b/mobile/android/base/home/TabMenuStrip.java @@ -0,0 +1,202 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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 android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.graphics.Rect; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.accessibility.AccessibilityEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewParent; +import android.view.ViewTreeObserver; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.util.Log; + +import org.mozilla.gecko.home.HomePager; +import org.mozilla.gecko.R; + +public class TabMenuStrip extends LinearLayout + implements HomePager.Decor, + View.OnFocusChangeListener { + private static final String LOGTAG = "GeckoTabMenuStrip"; + + private HomePager.OnTitleClickListener mOnTitleClickListener; + private Drawable mStrip; + private View mSelectedView; + + // Data associated with the scrolling of the strip drawable. + private View toTab; + private View fromTab; + private float progress; + + // This variable is used to predict the direction of scroll. + private float mPrevProgress; + + public TabMenuStrip(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabMenuStrip); + final int stripResId = a.getResourceId(R.styleable.TabMenuStrip_strip, -1); + a.recycle(); + + if (stripResId != -1) { + mStrip = getResources().getDrawable(stripResId); + } + + setWillNotDraw(false); + } + + @Override + public void onAddPagerView(String title) { + final TextView button = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.tab_menu_strip, this, false); + button.setText(title.toUpperCase()); + + addView(button); + button.setOnClickListener(new ViewClickListener(getChildCount() - 1)); + button.setOnFocusChangeListener(this); + } + + @Override + public void removeAllPagerViews() { + removeAllViews(); + } + + @Override + public void onPageSelected(final int position) { + mSelectedView = getChildAt(position); + + // Callback to measure and draw the strip after the view is visible. + ViewTreeObserver vto = mSelectedView.getViewTreeObserver(); + if (vto.isAlive()) { + vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + mSelectedView.getViewTreeObserver().removeGlobalOnLayoutListener(this); + + if (mStrip != null) { + mStrip.setBounds(mSelectedView.getLeft(), + mSelectedView.getTop(), + mSelectedView.getRight(), + mSelectedView.getBottom()); + } + + mPrevProgress = position; + } + }); + } + } + + // Page scroll animates the drawable and it's bounds from the previous to next child view. + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + if (mStrip == null) { + return; + } + + setScrollingData(position, positionOffset); + + final int fromTabLeft = fromTab.getLeft(); + final int fromTabRight = fromTab.getRight(); + + final int toTabLeft = toTab.getLeft(); + final int toTabRight = toTab.getRight(); + + mStrip.setBounds((int) (fromTabLeft + ((toTabLeft - fromTabLeft) * progress)), + 0, + (int) (fromTabRight + ((toTabRight - fromTabRight) * progress)), + getHeight()); + invalidate(); + } + + /* + * position + positionOffset goes from 0 to 2 as we scroll from page 1 to 3. + * Normalized progress is relative to the the direction the page is being scrolled towards. + * For this, we maintain direction of scroll with a state, and the child view we are moving towards and away from. + */ + private void setScrollingData(int position, float positionOffset) { + if (position >= getChildCount() - 1) { + return; + } + + final float currProgress = position + positionOffset; + + if (mPrevProgress > currProgress) { + toTab = getChildAt(position); + fromTab = getChildAt(position + 1); + progress = 1 - positionOffset; + } else { + toTab = getChildAt(position + 1); + fromTab = getChildAt(position); + progress = positionOffset; + } + + mPrevProgress = currProgress; + } + + @Override + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (mStrip != null) { + mStrip.draw(canvas); + } + } + + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (v == this && hasFocus && getChildCount() > 0) { + mSelectedView.requestFocus(); + return; + } + + if (!hasFocus) { + return; + } + + int i = 0; + final int numTabs = getChildCount(); + + while (i < numTabs) { + View view = getChildAt(i); + if (view == v) { + view.requestFocus(); + if (isShown()) { + // A view is focused so send an event to announce the menu strip state. + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + } + break; + } + + i++; + } + } + + @Override + public void setOnTitleClickListener(HomePager.OnTitleClickListener onTitleClickListener) { + mOnTitleClickListener = onTitleClickListener; + } + + private class ViewClickListener implements OnClickListener { + private final int mIndex; + + public ViewClickListener(int index) { + mIndex = index; + } + + @Override + public void onClick(View view) { + if (mOnTitleClickListener != null) { + mOnTitleClickListener.onTitleClicked(mIndex); + } + } + } +} diff --git a/mobile/android/base/resources/drawable-hdpi/home_tab_menu_strip.9.png b/mobile/android/base/resources/drawable-hdpi/home_tab_menu_strip.9.png new file mode 100644 index 00000000000..9ad4464c910 Binary files /dev/null and b/mobile/android/base/resources/drawable-hdpi/home_tab_menu_strip.9.png differ diff --git a/mobile/android/base/resources/drawable-mdpi/home_tab_menu_strip.9.png b/mobile/android/base/resources/drawable-mdpi/home_tab_menu_strip.9.png new file mode 100644 index 00000000000..9ad4464c910 Binary files /dev/null and b/mobile/android/base/resources/drawable-mdpi/home_tab_menu_strip.9.png differ diff --git a/mobile/android/base/resources/drawable-xhdpi/home_tab_menu_strip.9.png b/mobile/android/base/resources/drawable-xhdpi/home_tab_menu_strip.9.png new file mode 100644 index 00000000000..448235e4109 Binary files /dev/null and b/mobile/android/base/resources/drawable-xhdpi/home_tab_menu_strip.9.png differ diff --git a/mobile/android/base/resources/layout-large-v11/home_pager.xml b/mobile/android/base/resources/layout-large-v11/home_pager.xml new file mode 100644 index 00000000000..99656121372 --- /dev/null +++ b/mobile/android/base/resources/layout-large-v11/home_pager.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + diff --git a/mobile/android/base/resources/layout/gecko_app.xml b/mobile/android/base/resources/layout/gecko_app.xml index 1f0b18cc830..1a7f32a9f7e 100644 --- a/mobile/android/base/resources/layout/gecko_app.xml +++ b/mobile/android/base/resources/layout/gecko_app.xml @@ -31,23 +31,7 @@ android:layout_width="fill_parent" android:layout_height="fill_parent"> - - - - - + diff --git a/mobile/android/base/resources/layout/home_pager.xml b/mobile/android/base/resources/layout/home_pager.xml new file mode 100644 index 00000000000..2b445376045 --- /dev/null +++ b/mobile/android/base/resources/layout/home_pager.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/mobile/android/base/resources/layout/tab_menu_strip.xml b/mobile/android/base/resources/layout/tab_menu_strip.xml new file mode 100644 index 00000000000..f3505a9da14 --- /dev/null +++ b/mobile/android/base/resources/layout/tab_menu_strip.xml @@ -0,0 +1,15 @@ + + + + diff --git a/mobile/android/base/resources/values/attrs.xml b/mobile/android/base/resources/values/attrs.xml index 01a52610d80..0d07356ee7a 100644 --- a/mobile/android/base/resources/values/attrs.xml +++ b/mobile/android/base/resources/values/attrs.xml @@ -216,5 +216,9 @@ + + + + diff --git a/mobile/android/base/resources/values/dimens.xml b/mobile/android/base/resources/values/dimens.xml index 3dea9a3b79f..9ac3d9510f2 100644 --- a/mobile/android/base/resources/values/dimens.xml +++ b/mobile/android/base/resources/values/dimens.xml @@ -65,6 +65,8 @@ 16dip 2dp 200dp + 100dp + 18dp 156dp 47dp 58dp diff --git a/mobile/android/base/resources/values/styles.xml b/mobile/android/base/resources/values/styles.xml index ef32e36e47a..a98b44671d2 100644 --- a/mobile/android/base/resources/values/styles.xml +++ b/mobile/android/base/resources/values/styles.xml @@ -293,6 +293,11 @@ ?android:attr/textColorHint + +