mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1203036 - Put tab tray improvements behind nightly flag; r=mcomella
This patch reintroduced changes behind a nightly flag removed by: 1161638: Remove the chrome at the bottom of the screen in the Tabs Tray 1164723: Inherit from Tablet UI on Mobile UI (aka compact tabs) 1193745: Implement the tablet tabs tray grid view on mobile I've also done a bit of work to allow the chrome to sit at the correct Y location in landscape on mobile devices when the tabs panel is shown to account for bug 1193374 which adjusts the aspect ratio of the tabs panel thumnbnails and didn't need to be hidden behind a nightly flag. Tablets remain unaffected by this change.
This commit is contained in:
parent
35b1398f12
commit
23f7e684b7
@ -449,6 +449,8 @@ public class BrowserApp extends GeckoApp
|
||||
final View view;
|
||||
if (BrowserToolbar.class.getName().equals(name)) {
|
||||
view = BrowserToolbar.create(context, attrs);
|
||||
} else if (TabsPanel.TabsLayout.class.getName().equals(name)) {
|
||||
view = TabsPanel.createTabsLayout(context, attrs);
|
||||
} else {
|
||||
view = super.onCreateView(name, context, attrs);
|
||||
}
|
||||
|
@ -472,6 +472,7 @@ gbjar.sources += [
|
||||
'tabs/TabsGridLayout.java',
|
||||
'tabs/TabsLayoutAdapter.java',
|
||||
'tabs/TabsLayoutItemView.java',
|
||||
'tabs/TabsListLayout.java',
|
||||
'tabs/TabsPanel.java',
|
||||
'tabs/TabsPanelThumbnailView.java',
|
||||
'Telemetry.java',
|
||||
|
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!-- 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/. -->
|
||||
|
||||
<org.mozilla.gecko.tabs.TabsLayoutItemView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/TabsItem"
|
||||
android:focusable="true"
|
||||
android:id="@+id/info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/tab_vertical_padding"
|
||||
android:paddingBottom="@dimen/tab_vertical_padding"
|
||||
android:paddingLeft="1dip"
|
||||
android:paddingRight="1dip"
|
||||
android:gravity="center">
|
||||
|
||||
<!-- We set state_private on this View dynamically in TabsListLayout. -->
|
||||
<org.mozilla.gecko.widget.TabThumbnailWrapper
|
||||
android:id="@+id/wrapper"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/tab_thumbnail_margin"
|
||||
android:padding="@dimen/tab_thumbnail_padding"
|
||||
android:background="@drawable/tab_thumbnail"
|
||||
android:duplicateParentState="true">
|
||||
|
||||
<org.mozilla.gecko.tabs.TabsPanelThumbnailView android:id="@+id/thumbnail"
|
||||
android:layout_width="@dimen/tab_thumbnail_width"
|
||||
android:layout_height="@dimen/tab_thumbnail_height"
|
||||
android:layout_above="@+id/title_bar"
|
||||
android:layout_alignParentTop="true"/>
|
||||
|
||||
<LinearLayout android:id="@id/title_bar"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_width="@dimen/tab_thumbnail_width"
|
||||
android:layout_height="@dimen/tab_title_height"
|
||||
android:orientation="horizontal"
|
||||
android:background="#EFFF"
|
||||
android:duplicateParentState="true">
|
||||
|
||||
<TextView android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:padding="4dip"
|
||||
style="@style/TabLayoutItemTextAppearance"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@color/placeholder_active_grey"
|
||||
android:singleLine="true"
|
||||
android:duplicateParentState="true"/>
|
||||
|
||||
<ImageButton android:id="@+id/close"
|
||||
style="@style/TabsItemClose"
|
||||
android:layout_width="32dip"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/action_bar_button_inverse"
|
||||
android:scaleType="center"
|
||||
android:contentDescription="@string/close_tab"
|
||||
android:src="@drawable/tab_close"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</org.mozilla.gecko.widget.TabThumbnailWrapper>
|
||||
|
||||
</org.mozilla.gecko.tabs.TabsLayoutItemView>
|
@ -14,7 +14,7 @@
|
||||
|
||||
<!-- Note: for an unknown reason, scrolling in the TabsLayout
|
||||
does not work unless it is laid out after the empty view. -->
|
||||
<org.mozilla.gecko.tabs.TabsGridLayout
|
||||
<view class="org.mozilla.gecko.tabs.TabsPanel$TabsLayout"
|
||||
android:id="@+id/private_tabs_layout"
|
||||
style="@style/TabsLayout"
|
||||
android:layout_width="match_parent"
|
||||
|
62
mobile/android/base/resources/layout/tabs_list_item_view.xml
Normal file
62
mobile/android/base/resources/layout/tabs_list_item_view.xml
Normal file
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<org.mozilla.gecko.tabs.TabsLayoutItemView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/TabsItem"
|
||||
android:focusable="true"
|
||||
android:id="@+id/info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="12dip"
|
||||
android:paddingTop="6dip"
|
||||
android:paddingBottom="6dip"
|
||||
android:background="@drawable/tab_row">
|
||||
|
||||
<!-- We set state_private on this View dynamically in TabsListLayout. -->
|
||||
<org.mozilla.gecko.widget.TabThumbnailWrapper
|
||||
android:id="@+id/wrapper"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="4dip"
|
||||
android:background="@drawable/tab_thumbnail"
|
||||
android:duplicateParentState="true">
|
||||
|
||||
<org.mozilla.gecko.tabs.TabsPanelThumbnailView android:id="@+id/thumbnail"
|
||||
android:layout_width="@dimen/tab_thumbnail_width"
|
||||
android:layout_height="@dimen/tab_thumbnail_height"/>
|
||||
|
||||
</org.mozilla.gecko.widget.TabThumbnailWrapper>
|
||||
|
||||
<LinearLayout android:layout_width="0dip"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_weight="1.0"
|
||||
android:paddingTop="4dip"
|
||||
android:paddingLeft="8dip"
|
||||
android:paddingRight="4dip">
|
||||
|
||||
<TextView android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dip"
|
||||
android:layout_weight="1.0"
|
||||
style="@style/TabLayoutItemTextAppearance"
|
||||
android:textColor="#FFFFFFFF"
|
||||
android:textSize="14sp"
|
||||
android:singleLine="false"
|
||||
android:maxLines="4"
|
||||
android:duplicateParentState="true"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageButton android:id="@+id/close"
|
||||
style="@style/TabsItemClose"
|
||||
android:layout_width="34dip"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/action_bar_button_inverse"
|
||||
android:scaleType="center"
|
||||
android:contentDescription="@string/close_tab"
|
||||
android:src="@drawable/tab_close"/>
|
||||
|
||||
</org.mozilla.gecko.tabs.TabsLayoutItemView>
|
@ -70,12 +70,12 @@
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<FrameLayout
|
||||
<view class="org.mozilla.gecko.tabs.TabsPanel$TabsLayoutContainer"
|
||||
android:id="@+id/tabs_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.mozilla.gecko.tabs.TabsGridLayout
|
||||
<view class="org.mozilla.gecko.tabs.TabsPanel$TabsLayout"
|
||||
android:id="@+id/normal_tabs"
|
||||
style="@style/TabsLayout"
|
||||
android:layout_width="match_parent"
|
||||
@ -90,6 +90,6 @@
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</FrameLayout>
|
||||
</view>
|
||||
|
||||
</merge>
|
||||
|
@ -137,7 +137,6 @@
|
||||
<dimen name="tabs_strip_button_width">100dp</dimen>
|
||||
<dimen name="tabs_strip_button_padding">18dp</dimen>
|
||||
<dimen name="tabs_strip_shadow_size">1dp</dimen>
|
||||
<dimen name="tabs_layout_horizontal_height">156dp</dimen>
|
||||
<dimen name="text_selection_handle_width">47dp</dimen>
|
||||
<dimen name="text_selection_handle_height">58dp</dimen>
|
||||
<dimen name="text_selection_handle_shadow">11dp</dimen>
|
||||
@ -152,6 +151,10 @@
|
||||
<dimen name="tab_panel_grid_padding_top">19dp</dimen>
|
||||
|
||||
<dimen name="tab_highlight_stroke_width">4dp</dimen>
|
||||
<dimen name="tab_title_height">22dp</dimen>
|
||||
<dimen name="tab_vertical_padding">6dp</dimen>
|
||||
<dimen name="tab_thumbnail_padding">4dp</dimen>
|
||||
<dimen name="tab_thumbnail_margin">6dp</dimen>
|
||||
|
||||
<!-- PageActionButtons dimensions -->
|
||||
<dimen name="page_action_button_width">32dp</dimen>
|
||||
|
@ -10,4 +10,4 @@
|
||||
provide dummy values below. -->
|
||||
<item type="layout" name="tab_strip">@null</item>
|
||||
<item type="layout" name="tabs_panel_back_button">@null</item>
|
||||
</resources>
|
||||
</resources>
|
@ -51,6 +51,11 @@ class PrivateTabsPanel extends FrameLayout implements CloseAllPanelView {
|
||||
tabsLayout.hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExpand() {
|
||||
return tabsLayout.shouldExpand();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeAll() {
|
||||
tabsLayout.closeAll();
|
||||
|
@ -185,6 +185,11 @@ class TabsGridLayout extends GridView
|
||||
tabsAdapter.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExpand() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void autoHidePanel() {
|
||||
tabsPanel.autoHidePanel();
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
package org.mozilla.gecko.tabs;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.R;
|
||||
@ -100,7 +101,9 @@ public class TabsLayoutItemView extends LinearLayout
|
||||
mCloseButton = (ImageView) findViewById(R.id.close);
|
||||
mThumbnailWrapper = (TabThumbnailWrapper) findViewById(R.id.wrapper);
|
||||
|
||||
growCloseButtonHitArea();
|
||||
if (HardwareUtils.isTablet() || AppConstants.NIGHTLY_BUILD) {
|
||||
growCloseButtonHitArea();
|
||||
}
|
||||
}
|
||||
|
||||
private void growCloseButtonHitArea() {
|
||||
|
657
mobile/android/base/tabs/TabsListLayout.java
Normal file
657
mobile/android/base/tabs/TabsListLayout.java
Normal file
@ -0,0 +1,657 @@
|
||||
/* -*- 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.tabs;
|
||||
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.Tab;
|
||||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator.Property;
|
||||
import org.mozilla.gecko.animation.ViewHelper;
|
||||
import org.mozilla.gecko.tabs.TabsPanel.TabsLayout;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.widget.TwoWayView;
|
||||
import org.mozilla.gecko.widget.themed.ThemedRelativeLayout;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class TabsListLayout extends TwoWayView
|
||||
implements TabsLayout,
|
||||
Tabs.OnTabsChangedListener {
|
||||
|
||||
private static final String LOGTAG = "Gecko" + TabsListLayout.class.getSimpleName();
|
||||
|
||||
// Time to animate non-flinged tabs of screen, in milliseconds
|
||||
private static final int ANIMATION_DURATION = 250;
|
||||
|
||||
// Time between starting successive tab animations in closeAllTabs.
|
||||
private static final int ANIMATION_CASCADE_DELAY = 75;
|
||||
|
||||
private final boolean isPrivate;
|
||||
private final TabsLayoutAdapter tabsAdapter;
|
||||
private final List<View> pendingClosedTabs;
|
||||
private TabsPanel tabsPanel;
|
||||
private int closeAnimationCount;
|
||||
private int closeAllAnimationCount;
|
||||
private int originalSize;
|
||||
|
||||
public TabsListLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
pendingClosedTabs = new ArrayList<View>();
|
||||
|
||||
setItemsCanFocus(true);
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsLayout);
|
||||
isPrivate = (a.getInt(R.styleable.TabsLayout_tabs, 0x0) == 1);
|
||||
a.recycle();
|
||||
|
||||
tabsAdapter = new TabsListLayoutAdapter(context);
|
||||
setAdapter(tabsAdapter);
|
||||
|
||||
final TabSwipeGestureListener swipeListener = new TabSwipeGestureListener();
|
||||
setOnTouchListener(swipeListener);
|
||||
setOnScrollListener(swipeListener.makeScrollListener());
|
||||
setRecyclerListener(new RecyclerListener() {
|
||||
@Override
|
||||
public void onMovedToScrapHeap(View view) {
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) view;
|
||||
item.setThumbnail(null);
|
||||
item.setCloseVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTabsPanel(TabsPanel panel) {
|
||||
tabsPanel = panel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
setVisibility(View.VISIBLE);
|
||||
Tabs.getInstance().refreshThumbnails();
|
||||
Tabs.registerOnTabsChangedListener(this);
|
||||
refreshTabsData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hide() {
|
||||
setVisibility(View.GONE);
|
||||
Tabs.unregisterOnTabsChangedListener(this);
|
||||
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Screenshot:Cancel", ""));
|
||||
tabsAdapter.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExpand() {
|
||||
return isVertical();
|
||||
}
|
||||
|
||||
private void autoHidePanel() {
|
||||
tabsPanel.autoHidePanel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
|
||||
switch (msg) {
|
||||
case ADDED:
|
||||
// Refresh the list to make sure the new tab is added in the right position.
|
||||
refreshTabsData();
|
||||
break;
|
||||
|
||||
case CLOSED:
|
||||
if (tab.isPrivate() == isPrivate && tabsAdapter.getCount() > 0) {
|
||||
if (tabsAdapter.removeTab(tab)) {
|
||||
int selected = tabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
|
||||
updateSelectedStyle(selected);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SELECTED:
|
||||
// Update the selected position, then fall through...
|
||||
updateSelectedPosition();
|
||||
case UNSELECTED:
|
||||
// We just need to update the style for the unselected tab...
|
||||
case THUMBNAIL:
|
||||
case TITLE:
|
||||
case RECORDING_CHANGE:
|
||||
case AUDIO_PLAYING_CHANGE:
|
||||
View view = getChildAt(tabsAdapter.getPositionForTab(tab) - getFirstVisiblePosition());
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) view;
|
||||
item.assignValues(tab);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Updates the selected position in the list so that it will be scrolled to the right place.
|
||||
private void updateSelectedPosition() {
|
||||
int selected = tabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
|
||||
updateSelectedStyle(selected);
|
||||
if (selected != -1) {
|
||||
setSelection(selected);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the selected/unselected style for the tabs.
|
||||
*
|
||||
* @param selected position of the selected tab
|
||||
*/
|
||||
private void updateSelectedStyle(int selected) {
|
||||
for (int i = 0; i < tabsAdapter.getCount(); i++) {
|
||||
setItemChecked(i, (i == selected));
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshTabsData() {
|
||||
// Store a different copy of the tabs, so that we don't have to worry about
|
||||
// accidentally updating it on the wrong thread.
|
||||
ArrayList<Tab> tabData = new ArrayList<Tab>();
|
||||
Iterable<Tab> allTabs = Tabs.getInstance().getTabsInOrder();
|
||||
|
||||
for (Tab tab : allTabs) {
|
||||
if (tab.isPrivate() == isPrivate) {
|
||||
tabData.add(tab);
|
||||
}
|
||||
}
|
||||
|
||||
tabsAdapter.setTabs(tabData);
|
||||
updateSelectedPosition();
|
||||
}
|
||||
|
||||
public void resetTransforms(View view) {
|
||||
ViewHelper.setAlpha(view, 1);
|
||||
|
||||
if (isVertical()) {
|
||||
ViewHelper.setTranslationX(view, 0);
|
||||
} else {
|
||||
ViewHelper.setTranslationY(view, 0);
|
||||
}
|
||||
|
||||
// We only need to reset the height or width after individual tab close animations.
|
||||
if (originalSize != 0) {
|
||||
if (isVertical()) {
|
||||
ViewHelper.setHeight(view, originalSize);
|
||||
} else {
|
||||
ViewHelper.setWidth(view, originalSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isVertical() {
|
||||
return (getOrientation().compareTo(TwoWayView.Orientation.VERTICAL) == 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeAll() {
|
||||
final int childCount = getChildCount();
|
||||
|
||||
// Just close the panel if there are no tabs to close.
|
||||
if (childCount == 0) {
|
||||
autoHidePanel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable the view so that gestures won't interfere wth the tab close animation.
|
||||
setEnabled(false);
|
||||
|
||||
// Delay starting each successive animation to create a cascade effect.
|
||||
int cascadeDelay = 0;
|
||||
for (int i = childCount - 1; i >= 0; i--) {
|
||||
final View view = getChildAt(i);
|
||||
|
||||
final PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION);
|
||||
animator.attach(view, Property.ALPHA, 0);
|
||||
|
||||
if (isVertical()) {
|
||||
animator.attach(view, Property.TRANSLATION_X, view.getWidth());
|
||||
} else {
|
||||
animator.attach(view, Property.TRANSLATION_Y, view.getHeight());
|
||||
}
|
||||
|
||||
closeAllAnimationCount++;
|
||||
|
||||
animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
@Override
|
||||
public void onPropertyAnimationStart() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPropertyAnimationEnd() {
|
||||
closeAllAnimationCount--;
|
||||
if (closeAllAnimationCount > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide the panel after the animation is done.
|
||||
autoHidePanel();
|
||||
|
||||
// Re-enable the view after the animation is done.
|
||||
TabsListLayout.this.setEnabled(true);
|
||||
|
||||
// Then actually close all the tabs.
|
||||
final Iterable<Tab> tabs = Tabs.getInstance().getTabsInOrder();
|
||||
|
||||
for (Tab tab : tabs) {
|
||||
// In the normal panel we want to close all tabs (both private and normal),
|
||||
// but in the private panel we only want to close private tabs.
|
||||
if (!isPrivate || tab.isPrivate()) {
|
||||
Tabs.getInstance().closeTab(tab, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ThreadUtils.getUiHandler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
animator.start();
|
||||
}
|
||||
}, cascadeDelay);
|
||||
|
||||
cascadeDelay += ANIMATION_CASCADE_DELAY;
|
||||
}
|
||||
}
|
||||
|
||||
private void animateClose(final View view, int pos) {
|
||||
PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION);
|
||||
animator.attach(view, Property.ALPHA, 0);
|
||||
|
||||
if (isVertical()) {
|
||||
animator.attach(view, Property.TRANSLATION_X, pos);
|
||||
} else {
|
||||
animator.attach(view, Property.TRANSLATION_Y, pos);
|
||||
}
|
||||
|
||||
closeAnimationCount++;
|
||||
|
||||
pendingClosedTabs.add(view);
|
||||
|
||||
animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
@Override
|
||||
public void onPropertyAnimationStart() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPropertyAnimationEnd() {
|
||||
closeAnimationCount--;
|
||||
if (closeAnimationCount > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (View pendingView : pendingClosedTabs) {
|
||||
animateFinishClose(pendingView);
|
||||
}
|
||||
pendingClosedTabs.clear();
|
||||
}
|
||||
});
|
||||
|
||||
if (tabsAdapter.getCount() == 1) {
|
||||
autoHidePanel();
|
||||
}
|
||||
|
||||
animator.start();
|
||||
}
|
||||
|
||||
private void animateFinishClose(final View view) {
|
||||
final boolean isVertical = isVertical();
|
||||
|
||||
PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION);
|
||||
|
||||
if (isVertical) {
|
||||
animator.attach(view, Property.HEIGHT, 1);
|
||||
} else {
|
||||
animator.attach(view, Property.WIDTH, 1);
|
||||
}
|
||||
|
||||
final int tabId = ((TabsLayoutItemView) view).getTabId();
|
||||
|
||||
// Caching this assumes that all rows are the same height
|
||||
if (originalSize == 0) {
|
||||
originalSize = (isVertical ? view.getHeight() : view.getWidth());
|
||||
}
|
||||
|
||||
animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
@Override
|
||||
public void onPropertyAnimationStart() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPropertyAnimationEnd() {
|
||||
Tabs tabs = Tabs.getInstance();
|
||||
Tab tab = tabs.getTab(tabId);
|
||||
tabs.closeTab(tab, true);
|
||||
}
|
||||
});
|
||||
|
||||
animator.start();
|
||||
}
|
||||
|
||||
private void animateCancel(final View view) {
|
||||
PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION);
|
||||
animator.attach(view, Property.ALPHA, 1);
|
||||
|
||||
if (isVertical()) {
|
||||
animator.attach(view, Property.TRANSLATION_X, 0);
|
||||
} else {
|
||||
animator.attach(view, Property.TRANSLATION_Y, 0);
|
||||
}
|
||||
|
||||
animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
|
||||
@Override
|
||||
public void onPropertyAnimationStart() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPropertyAnimationEnd() {
|
||||
TabsLayoutItemView tab = (TabsLayoutItemView) view;
|
||||
tab.setCloseVisible(true);
|
||||
}
|
||||
});
|
||||
|
||||
animator.start();
|
||||
}
|
||||
|
||||
private class TabsListLayoutAdapter extends TabsLayoutAdapter {
|
||||
private final Button.OnClickListener mCloseOnClickListener;
|
||||
|
||||
public TabsListLayoutAdapter(Context context) {
|
||||
super(context, R.layout.tabs_list_item_view);
|
||||
mCloseOnClickListener = new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// The view here is the close button, which has a reference
|
||||
// to the parent TabsLayoutItemView in it's tag, hence the getTag() call
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) v.getTag();
|
||||
final int pos = (isVertical() ? item.getWidth() : 0 - item.getHeight());
|
||||
animateClose(item, pos);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabsLayoutItemView newView(int position, ViewGroup parent) {
|
||||
TabsLayoutItemView item = super.newView(position, parent);
|
||||
item.setCloseOnClickListener(mCloseOnClickListener);
|
||||
((ThemedRelativeLayout) item.findViewById(R.id.wrapper)).setPrivateMode(isPrivate);
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(TabsLayoutItemView view, Tab tab) {
|
||||
super.bindView(view, tab);
|
||||
// If we're recycling this view, there's a chance it was transformed during
|
||||
// the close animation. Remove any of those properties.
|
||||
resetTransforms(view);
|
||||
}
|
||||
}
|
||||
|
||||
private class TabSwipeGestureListener implements View.OnTouchListener {
|
||||
// same value the stock browser uses for after drag animation velocity in pixels/sec
|
||||
// http://androidxref.com/4.0.4/xref/packages/apps/Browser/src/com/android/browser/NavTabScroller.java#61
|
||||
private static final float MIN_VELOCITY = 750;
|
||||
|
||||
private final int swipeThreshold;
|
||||
private final int minFlingVelocity;
|
||||
private final int maxFlingVelocity;
|
||||
private VelocityTracker velocityTracker;
|
||||
private int listWidth = 1;
|
||||
private int listHeight = 1;
|
||||
private View swipeView;
|
||||
private Runnable pendingCheckForTap;
|
||||
private float swipeStartX;
|
||||
private float swipeStartY;
|
||||
private boolean swiping;
|
||||
private boolean enabled;
|
||||
|
||||
public TabSwipeGestureListener() {
|
||||
enabled = true;
|
||||
|
||||
ViewConfiguration vc = ViewConfiguration.get(TabsListLayout.this.getContext());
|
||||
|
||||
swipeThreshold = vc.getScaledTouchSlop();
|
||||
minFlingVelocity = (int) (getContext().getResources().getDisplayMetrics().density * MIN_VELOCITY);
|
||||
maxFlingVelocity = vc.getScaledMaximumFlingVelocity();
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public TwoWayView.OnScrollListener makeScrollListener() {
|
||||
return new TwoWayView.OnScrollListener() {
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(TwoWayView twoWayView, int scrollState) {
|
||||
setEnabled(scrollState != TwoWayView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(TwoWayView twoWayView, int i, int i1, int i2) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View view, MotionEvent e) {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (listWidth < 2 || listHeight < 2) {
|
||||
listWidth = TabsListLayout.this.getWidth();
|
||||
listHeight = TabsListLayout.this.getHeight();
|
||||
}
|
||||
|
||||
switch (e.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
// Check if we should set pressed state on the
|
||||
// touched view after a standard delay.
|
||||
triggerCheckForTap();
|
||||
|
||||
final float x = e.getRawX();
|
||||
final float y = e.getRawY();
|
||||
|
||||
// Find out which view is being touched
|
||||
swipeView = findViewAt(x, y);
|
||||
|
||||
if (swipeView != null) {
|
||||
swipeStartX = e.getRawX();
|
||||
swipeStartY = e.getRawY();
|
||||
velocityTracker = VelocityTracker.obtain();
|
||||
velocityTracker.addMovement(e);
|
||||
}
|
||||
|
||||
view.onTouchEvent(e);
|
||||
return true;
|
||||
}
|
||||
case MotionEvent.ACTION_UP: {
|
||||
if (swipeView == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
cancelCheckForTap();
|
||||
swipeView.setPressed(false);
|
||||
|
||||
if (!swiping) {
|
||||
TabsLayoutItemView item = (TabsLayoutItemView) swipeView;
|
||||
Tabs.getInstance().selectTab(item.getTabId());
|
||||
autoHidePanel();
|
||||
velocityTracker.recycle();
|
||||
velocityTracker = null;
|
||||
break;
|
||||
}
|
||||
|
||||
velocityTracker.addMovement(e);
|
||||
velocityTracker.computeCurrentVelocity(1000, maxFlingVelocity);
|
||||
|
||||
float velocityX = Math.abs(velocityTracker.getXVelocity());
|
||||
float velocityY = Math.abs(velocityTracker.getYVelocity());
|
||||
boolean dismiss = false;
|
||||
boolean dismissDirection = false;
|
||||
int dismissTranslation;
|
||||
|
||||
if (isVertical()) {
|
||||
float deltaX = ViewHelper.getTranslationX(swipeView);
|
||||
|
||||
if (Math.abs(deltaX) > listWidth / 2) {
|
||||
dismiss = true;
|
||||
dismissDirection = (deltaX > 0);
|
||||
} else if (minFlingVelocity <= velocityX && velocityX <= maxFlingVelocity
|
||||
&& velocityY < velocityX) {
|
||||
|
||||
dismiss = swiping && (deltaX * velocityTracker.getXVelocity() > 0);
|
||||
dismissDirection = (velocityTracker.getXVelocity() > 0);
|
||||
}
|
||||
|
||||
dismissTranslation = (dismissDirection ? listWidth : -listWidth);
|
||||
} else {
|
||||
float deltaY = ViewHelper.getTranslationY(swipeView);
|
||||
|
||||
if (Math.abs(deltaY) > listHeight / 2) {
|
||||
dismiss = true;
|
||||
dismissDirection = (deltaY > 0);
|
||||
} else if (minFlingVelocity <= velocityY && velocityY <= maxFlingVelocity
|
||||
&& velocityX < velocityY) {
|
||||
|
||||
dismiss = swiping && (deltaY * velocityTracker.getYVelocity() > 0);
|
||||
dismissDirection = (velocityTracker.getYVelocity() > 0);
|
||||
}
|
||||
|
||||
dismissTranslation = (dismissDirection ? listHeight : -listHeight);
|
||||
}
|
||||
|
||||
if (dismiss) {
|
||||
animateClose(swipeView, dismissTranslation);
|
||||
} else {
|
||||
animateCancel(swipeView);
|
||||
}
|
||||
|
||||
velocityTracker.recycle();
|
||||
velocityTracker = null;
|
||||
swipeView = null;
|
||||
swipeStartX = 0;
|
||||
swipeStartY = 0;
|
||||
swiping = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
if (swipeView == null || velocityTracker == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
velocityTracker.addMovement(e);
|
||||
|
||||
final boolean isVertical = isVertical();
|
||||
float deltaX = e.getRawX() - swipeStartX;
|
||||
float deltaY = e.getRawY() - swipeStartY;
|
||||
float delta = (isVertical ? deltaX : deltaY);
|
||||
boolean isScrollingX = Math.abs(deltaX) > swipeThreshold;
|
||||
boolean isScrollingY = Math.abs(deltaY) > swipeThreshold;
|
||||
boolean isSwipingToClose = (isVertical ? isScrollingX : isScrollingY);
|
||||
|
||||
// If we're actually swiping, make sure we don't
|
||||
// set pressed state on the swiped view.
|
||||
if (isScrollingX || isScrollingY) {
|
||||
cancelCheckForTap();
|
||||
}
|
||||
|
||||
if (isSwipingToClose) {
|
||||
swiping = true;
|
||||
TabsListLayout.this.requestDisallowInterceptTouchEvent(true);
|
||||
((TabsLayoutItemView) swipeView).setCloseVisible(false);
|
||||
|
||||
// Stops listview from highlighting the touched item
|
||||
// in the list when swiping.
|
||||
MotionEvent cancelEvent = MotionEvent.obtain(e);
|
||||
cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
|
||||
(e.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
|
||||
|
||||
TabsListLayout.this.onTouchEvent(cancelEvent);
|
||||
cancelEvent.recycle();
|
||||
}
|
||||
if (swiping) {
|
||||
if (isVertical) {
|
||||
ViewHelper.setTranslationX(swipeView, delta);
|
||||
} else {
|
||||
ViewHelper.setTranslationY(swipeView, delta);
|
||||
}
|
||||
|
||||
ViewHelper.setAlpha(swipeView, Math.max(0.1f, Math.min(1f,
|
||||
1f - 2f * Math.abs(delta) / (isVertical ? listWidth : listHeight))));
|
||||
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private View findViewAt(float rawX, float rawY) {
|
||||
Rect rect = new Rect();
|
||||
|
||||
int[] listViewCoords = new int[2];
|
||||
TabsListLayout.this.getLocationOnScreen(listViewCoords);
|
||||
|
||||
int x = (int) rawX - listViewCoords[0];
|
||||
int y = (int) rawY - listViewCoords[1];
|
||||
|
||||
for (int i = 0; i < TabsListLayout.this.getChildCount(); i++) {
|
||||
View child = TabsListLayout.this.getChildAt(i);
|
||||
child.getHitRect(rect);
|
||||
|
||||
if (rect.contains(x, y)) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void triggerCheckForTap() {
|
||||
if (pendingCheckForTap == null) {
|
||||
pendingCheckForTap = new CheckForTap();
|
||||
}
|
||||
TabsListLayout.this.postDelayed(pendingCheckForTap, ViewConfiguration.getTapTimeout());
|
||||
}
|
||||
|
||||
private void cancelCheckForTap() {
|
||||
if (pendingCheckForTap == null) {
|
||||
return;
|
||||
}
|
||||
TabsListLayout.this.removeCallbacks(pendingCheckForTap);
|
||||
}
|
||||
|
||||
private class CheckForTap implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!swiping && swipeView != null && enabled) {
|
||||
swipeView.setPressed(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
|
||||
package org.mozilla.gecko.tabs;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.AppConstants.Versions;
|
||||
import org.mozilla.gecko.GeckoApp;
|
||||
import org.mozilla.gecko.GeckoApplication;
|
||||
@ -26,6 +27,7 @@ import org.mozilla.gecko.widget.IconTabWidget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
@ -55,6 +57,7 @@ public class TabsPanel extends LinearLayout
|
||||
void setTabsPanel(TabsPanel panel);
|
||||
void show();
|
||||
void hide();
|
||||
boolean shouldExpand();
|
||||
}
|
||||
|
||||
public interface CloseAllPanelView extends PanelView {
|
||||
@ -69,11 +72,20 @@ public class TabsPanel extends LinearLayout
|
||||
void onTabsLayoutChange(int width, int height);
|
||||
}
|
||||
|
||||
|
||||
public static View createTabsLayout(final Context context, final AttributeSet attrs) {
|
||||
if (HardwareUtils.isTablet() || AppConstants.NIGHTLY_BUILD) {
|
||||
return new TabsGridLayout(context, attrs);
|
||||
} else {
|
||||
return new TabsListLayout(context, attrs);
|
||||
}
|
||||
}
|
||||
|
||||
private final Context mContext;
|
||||
private final GeckoApp mActivity;
|
||||
private final LightweightTheme mTheme;
|
||||
private RelativeLayout mHeader;
|
||||
private FrameLayout mTabsContainer;
|
||||
private TabsLayoutContainer mTabsContainer;
|
||||
private PanelView mPanel;
|
||||
private PanelView mPanelNormal;
|
||||
private PanelView mPanelPrivate;
|
||||
@ -112,7 +124,7 @@ public class TabsPanel extends LinearLayout
|
||||
|
||||
private void initialize() {
|
||||
mHeader = (RelativeLayout) findViewById(R.id.tabs_panel_header);
|
||||
mTabsContainer = (FrameLayout) findViewById(R.id.tabs_container);
|
||||
mTabsContainer = (TabsLayoutContainer) findViewById(R.id.tabs_container);
|
||||
|
||||
mPanelNormal = (PanelView) findViewById(R.id.normal_tabs);
|
||||
mPanelNormal.setTabsPanel(this);
|
||||
@ -234,14 +246,40 @@ public class TabsPanel extends LinearLayout
|
||||
return mActivity.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private static int getTabContainerHeight(View tabsContainer) {
|
||||
private static int getTabContainerHeight(TabsLayoutContainer tabsContainer) {
|
||||
Resources resources = tabsContainer.getContext().getResources();
|
||||
|
||||
int screenHeight = resources.getDisplayMetrics().heightPixels;
|
||||
|
||||
int actionBarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height);
|
||||
|
||||
return screenHeight - actionBarHeight;
|
||||
if (HardwareUtils.isTablet() || AppConstants.NIGHTLY_BUILD) {
|
||||
return screenHeight - actionBarHeight;
|
||||
}
|
||||
|
||||
PanelView panelView = tabsContainer.getCurrentPanelView();
|
||||
if (panelView != null && !panelView.shouldExpand()) {
|
||||
|
||||
// This allows us to accommodate varying height tab previews across different devices.
|
||||
// We should be able to remove once we remove the list view and remove the chrome again
|
||||
return resources.getDimensionPixelSize(R.dimen.tab_thumbnail_height
|
||||
+ resources.getDimensionPixelSize(R.dimen.tab_title_height)
|
||||
+ 2 * (resources.getDimensionPixelSize(R.dimen.tab_highlight_stroke_width)
|
||||
+ resources.getDimensionPixelSize(R.dimen.tab_vertical_padding)
|
||||
+ resources.getDimensionPixelSize(R.dimen.tab_thumbnail_padding)
|
||||
+ resources.getDimensionPixelSize(R.dimen.tab_thumbnail_margin)));
|
||||
}
|
||||
|
||||
Rect windowRect = new Rect();
|
||||
tabsContainer.getWindowVisibleDisplayFrame(windowRect);
|
||||
int windowHeight = windowRect.bottom - windowRect.top;
|
||||
|
||||
// The web content area should have at least 1.5x the height of the action bar.
|
||||
// The tabs panel shouldn't take less than 50% of the screen height and can take
|
||||
// up to 80% of the window height.
|
||||
return (int) Math.max(screenHeight * 0.5f,
|
||||
Math.min(windowHeight - 2.5f * actionBarHeight, windowHeight * 0.8f) - actionBarHeight);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -279,6 +317,35 @@ public class TabsPanel extends LinearLayout
|
||||
onLightweightThemeChanged();
|
||||
}
|
||||
|
||||
static class TabsLayoutContainer extends FrameLayout {
|
||||
|
||||
public TabsLayoutContainer(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public PanelView getCurrentPanelView() {
|
||||
final int childCount = getChildCount();
|
||||
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = getChildAt(i);
|
||||
if (!(child instanceof PanelView)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child.getVisibility() == View.VISIBLE) {
|
||||
return (PanelView) child;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int heightSpec = MeasureSpec.makeMeasureSpec(getTabContainerHeight(TabsLayoutContainer.this), MeasureSpec.EXACTLY);
|
||||
super.onMeasure(widthMeasureSpec, heightSpec);
|
||||
}
|
||||
}
|
||||
|
||||
// Tabs Panel Toolbar contains the Buttons
|
||||
static class TabsPanelToolbar extends LinearLayout
|
||||
implements LightweightTheme.OnChangeListener {
|
||||
@ -422,6 +489,10 @@ public class TabsPanel extends LinearLayout
|
||||
return mVisible;
|
||||
}
|
||||
|
||||
public Panel getCurrentPanel() {
|
||||
return mCurrentPanel;
|
||||
}
|
||||
|
||||
public void setHWLayerEnabled(boolean enabled) {
|
||||
if (Versions.preHC) {
|
||||
return;
|
||||
|
Loading…
Reference in New Issue
Block a user