Commit b45ec874 authored by Yue Zhang's avatar Yue Zhang Committed by Commit Bot

Add TabGridIphItem component

This CL adds the MVC components for TabGridIph component. Right now
there are no consumers for this part of logic.

Bug: 987043
Change-Id: I877d5909a164fad79d1f7206652e98eab8c54efe
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1731178
Commit-Queue: Yue Zhang <yuezhanggg@chromium.org>
Reviewed-by: default avatarWei-Yin Chen (陳威尹) <wychen@chromium.org>
Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#684400}
parent 0f9ac276
...@@ -30,7 +30,11 @@ android_library("java") { ...@@ -30,7 +30,11 @@ android_library("java") {
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java", "java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java", "java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogParent.java", "java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogParent.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridIphItemCoordinator.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridIphItemMediator.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridIphItemProperties.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridIphItemView.java", "java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridIphItemView.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridIphItemViewBinder.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridItemTouchHelperCallback.java", "java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridItemTouchHelperCallback.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java", "java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetCoordinator.java",
"java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetMediator.java", "java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetMediator.java",
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
<org.chromium.chrome.browser.tasks.tab_management.TabGridIphItemView <org.chromium.chrome.browser.tasks.tab_management.TabGridIphItemView
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/tab_grid_iph_item"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/tab_grid_iph_card_height" android:layout_height="@dimen/tab_grid_iph_card_height"
android:layout_marginTop="@dimen/tab_grid_iph_card_padding" android:layout_marginTop="@dimen/tab_grid_iph_card_padding"
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.tasks.tab_management;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import org.chromium.chrome.tab_ui.R;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
/**
* Coordinator for the IPH item in {@link GridTabSwitcherCoordinator}
*/
class TabGridIphItemCoordinator {
private final PropertyModelChangeProcessor mModelChangeProcessor;
private final TabGridIphItemMediator mMediator;
TabGridIphItemCoordinator(Context context, TabListRecyclerView contentView, ViewGroup parent) {
PropertyModel iphItemPropertyModel = new PropertyModel(TabGridIphItemProperties.ALL_KEYS);
LayoutInflater.from(context).inflate(R.layout.iph_card_item_layout, parent, true);
TabGridIphItemView iphItemView = parent.findViewById(R.id.tab_grid_iph_item);
mModelChangeProcessor = PropertyModelChangeProcessor.create(iphItemPropertyModel,
new TabGridIphItemViewBinder.ViewHolder(contentView, iphItemView),
TabGridIphItemViewBinder::bind);
mMediator = new TabGridIphItemMediator(iphItemPropertyModel);
}
/** Destroy the IPH component. */
public void destroy() {
mModelChangeProcessor.destroy();
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.tasks.tab_management;
import android.view.View;
import org.chromium.chrome.browser.widget.ScrimView;
import org.chromium.ui.modelutil.PropertyModel;
/**
* A mediator for the TabGridIPHItem component, responsible for communicating
* with the components' coordinator as well as managing the business logic
* for IPH item show/hide.
*/
class TabGridIphItemMediator {
private PropertyModel mModel;
TabGridIphItemMediator(PropertyModel model) {
mModel = model;
setupOnClickListeners();
setupScrimViewObserver();
}
private void setupOnClickListeners() {
View.OnClickListener showIPHOnClickListener = view -> {
mModel.set(TabGridIphItemProperties.IS_IPH_DIALOG_VISIBLE, true);
// When the dialog is showing, the entrance should be closed.
mModel.set(TabGridIphItemProperties.IS_IPH_ENTRANCE_VISIBLE, false);
};
mModel.set(
TabGridIphItemProperties.IPH_ENTRANCE_SHOW_BUTTON_LISTENER, showIPHOnClickListener);
View.OnClickListener closeIPHDialogOnClickListener =
view -> mModel.set(TabGridIphItemProperties.IS_IPH_DIALOG_VISIBLE, false);
mModel.set(TabGridIphItemProperties.IPH_DIALOG_CLOSE_BUTTON_LISTENER,
closeIPHDialogOnClickListener);
View.OnClickListener closeIPHEntranceOnClickListener =
view -> mModel.set(TabGridIphItemProperties.IS_IPH_ENTRANCE_VISIBLE, false);
mModel.set(TabGridIphItemProperties.IPH_ENTRANCE_CLOSE_BUTTON_LISTENER,
closeIPHEntranceOnClickListener);
}
private void setupScrimViewObserver() {
ScrimView.ScrimObserver observer = new ScrimView.ScrimObserver() {
@Override
public void onScrimClick() {
mModel.set(TabGridIphItemProperties.IS_IPH_DIALOG_VISIBLE, false);
}
@Override
public void onScrimVisibilityChanged(boolean visible) {}
};
mModel.set(TabGridIphItemProperties.IPH_SCRIM_VIEW_OBSERVER, observer);
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.tasks.tab_management;
import android.view.View;
import org.chromium.chrome.browser.widget.ScrimView;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
class TabGridIphItemProperties {
public static final PropertyModel
.WritableObjectPropertyKey<View.OnClickListener> IPH_ENTRANCE_CLOSE_BUTTON_LISTENER =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyModel
.WritableObjectPropertyKey<View.OnClickListener> IPH_ENTRANCE_SHOW_BUTTON_LISTENER =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyModel
.WritableObjectPropertyKey<View.OnClickListener> IPH_DIALOG_CLOSE_BUTTON_LISTENER =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyModel
.WritableObjectPropertyKey<ScrimView.ScrimObserver> IPH_SCRIM_VIEW_OBSERVER =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyModel.WritableBooleanPropertyKey IS_IPH_DIALOG_VISIBLE =
new PropertyModel.WritableBooleanPropertyKey();
public static final PropertyModel.WritableBooleanPropertyKey IS_IPH_ENTRANCE_VISIBLE =
new PropertyModel.WritableBooleanPropertyKey();
public static final PropertyKey[] ALL_KEYS =
new PropertyKey[] {IPH_ENTRANCE_CLOSE_BUTTON_LISTENER,
IPH_ENTRANCE_SHOW_BUTTON_LISTENER, IPH_DIALOG_CLOSE_BUTTON_LISTENER,
IPH_SCRIM_VIEW_OBSERVER, IS_IPH_DIALOG_VISIBLE, IS_IPH_ENTRANCE_VISIBLE};
}
...@@ -22,15 +22,17 @@ import org.chromium.chrome.tab_ui.R; ...@@ -22,15 +22,17 @@ import org.chromium.chrome.tab_ui.R;
import org.chromium.ui.widget.ChromeImageView; import org.chromium.ui.widget.ChromeImageView;
/** /**
* Represents a view that works as the entrance for IPH in {@link TabListRecyclerView}. * Represents a view that works as the entrance for IPH in GridTabSwitcher.
*/ */
public class TabGridIphItemView extends FrameLayout { public class TabGridIphItemView extends FrameLayout {
private TextView mShowIPHButton; private View mIphDialogView;
private ChromeImageView mCloseIphEntranceButton; private TextView mShowIPHDialogButton;
private PopupWindow mIphWindow; private TextView mCloseIPHDialogButton;
private ChromeImageView mCloseIPHEntranceButton;
private ScrimView mScrimView; private ScrimView mScrimView;
private ScrimView.ScrimParams mScrimParams; private ScrimView.ScrimParams mScrimParams;
private Drawable mIphDrawable; private Drawable mIphDrawable;
private PopupWindow mIphWindow;
public TabGridIphItemView(Context context, AttributeSet attrs) { public TabGridIphItemView(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
...@@ -41,54 +43,73 @@ public class TabGridIphItemView extends FrameLayout { ...@@ -41,54 +43,73 @@ public class TabGridIphItemView extends FrameLayout {
super.onFinishInflate(); super.onFinishInflate();
FrameLayout backgroundView = new FrameLayout(getContext()); FrameLayout backgroundView = new FrameLayout(getContext());
View iphDialogView = mIphDialogView =
LayoutInflater.from(getContext()) LayoutInflater.from(getContext())
.inflate(R.layout.iph_drag_and_drop_dialog_layout, backgroundView, false); .inflate(R.layout.iph_drag_and_drop_dialog_layout, backgroundView, false);
mShowIPHButton = findViewById(R.id.show_me_button); mShowIPHDialogButton = findViewById(R.id.show_me_button);
mCloseIphEntranceButton = findViewById(R.id.close_iph_button); mCloseIPHEntranceButton = findViewById(R.id.close_iph_button);
mCloseIPHDialogButton =
mIphDialogView.findViewById(R.id.iph_drag_and_drop_dialog_close_button);
mIphDrawable = mIphDrawable =
((ImageView) iphDialogView.findViewById(R.id.animation_drawable)).getDrawable(); ((ImageView) mIphDialogView.findViewById(R.id.animation_drawable)).getDrawable();
TextView closeIphDialogButton = backgroundView.addView(mIphDialogView);
iphDialogView.findViewById(R.id.iph_drag_and_drop_dialog_close_button);
backgroundView.addView(iphDialogView);
mIphWindow = new PopupWindow(backgroundView, ViewGroup.LayoutParams.MATCH_PARENT, mIphWindow = new PopupWindow(backgroundView, ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT); ViewGroup.LayoutParams.MATCH_PARENT);
mScrimView = new ScrimView(getContext(), null, backgroundView); mScrimView = new ScrimView(getContext(), null, backgroundView);
ScrimView.ScrimObserver scrimObserver = new ScrimView.ScrimObserver() {
@Override
public void onScrimClick() {
mIphWindow.dismiss();
mScrimView.hideScrim(true);
}
@Override
public void onScrimVisibilityChanged(boolean visible) {}
};
mScrimParams = new ScrimView.ScrimParams(iphDialogView, false, true, 0, scrimObserver);
closeIphDialogButton.setOnClickListener(View -> scrimObserver.onScrimClick());
} }
/** /**
* Setup the {@link View.OnClickListener} for ShowIPH button. * Setup the {@link View.OnClickListener} for close button in the entrance of IPH.
*
* @param listener The {@link View.OnClickListener} used to setup close button in IPH entrance.
*/ */
@Override void setupCloseIphEntranceButtonOnclickListener(View.OnClickListener listener) {
protected void onAttachedToWindow() { mCloseIPHEntranceButton.setOnClickListener(listener);
super.onAttachedToWindow();
View parent = (View) getParent();
mShowIPHButton.setOnClickListener((View view) -> {
// When show me button is clicked, the entrance should be closed.
mCloseIphEntranceButton.performClick();
mScrimView.showScrim(mScrimParams);
mIphWindow.showAtLocation(parent, Gravity.CENTER, 0, 0);
((Animatable) mIphDrawable).start();
});
} }
/** /**
* Setup the {@link View.OnClickListener} for close button in the entrance of IPH. * Setup the {@link View.OnClickListener} for close button in the IPH dialog.
* *
* @param listener The {@link View.OnClickListener} used to setup close button in IPH entrance. * @param listener The {@link View.OnClickListener} used to setup close button in IPH dialog.
*/ */
void setupCloseIphEntranceButtonOnclickListener(View.OnClickListener listener) { void setupCloseIphDialogButtonOnclickListener(View.OnClickListener listener) {
mCloseIphEntranceButton.setOnClickListener(listener); mCloseIPHDialogButton.setOnClickListener(listener);
}
/**
* Setup the {@link View.OnClickListener} for the button that shows the IPH dialog.
*
* @param listener The {@link View.OnClickListener} used to setup show IPH button.
*/
void setupShowIphButtonOnclickListener(View.OnClickListener listener) {
mShowIPHDialogButton.setOnClickListener(listener);
}
/**
* Setup the {@link ScrimView.ScrimParams} for the {@link ScrimView}.
*
* @param observer The {@link ScrimView.ScrimObserver} used to setup the scrim view params.
*/
void setupIPHDialogScrimViewObserver(ScrimView.ScrimObserver observer) {
mScrimParams = new ScrimView.ScrimParams(mIphDialogView, false, true, 0, observer);
}
/**
* Close the IPH dialog and hide the scrim view.
*/
void closeIphDialog() {
mIphWindow.dismiss();
mScrimView.hideScrim(true);
}
/**
* Show the scrim view and show dialog above it.
*/
void showIPHDialog() {
if (mScrimParams != null) {
mScrimView.showScrim(mScrimParams);
}
mIphWindow.showAtLocation((View) getParent(), Gravity.CENTER, 0, 0);
((Animatable) mIphDrawable).start();
} }
} }
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.tasks.tab_management;
import static org.chromium.chrome.browser.tasks.tab_management.TabGridIphItemProperties.IPH_DIALOG_CLOSE_BUTTON_LISTENER;
import static org.chromium.chrome.browser.tasks.tab_management.TabGridIphItemProperties.IPH_ENTRANCE_CLOSE_BUTTON_LISTENER;
import static org.chromium.chrome.browser.tasks.tab_management.TabGridIphItemProperties.IPH_ENTRANCE_SHOW_BUTTON_LISTENER;
import static org.chromium.chrome.browser.tasks.tab_management.TabGridIphItemProperties.IPH_SCRIM_VIEW_OBSERVER;
import static org.chromium.chrome.browser.tasks.tab_management.TabGridIphItemProperties.IS_IPH_DIALOG_VISIBLE;
import static org.chromium.chrome.browser.tasks.tab_management.TabGridIphItemProperties.IS_IPH_ENTRANCE_VISIBLE;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
/**
* ViewBinder for TabGridIphItem.
*/
class TabGridIphItemViewBinder {
public static class ViewHolder {
public final TabListRecyclerView contentView;
public final TabGridIphItemView iphItemView;
ViewHolder(TabListRecyclerView contentView, TabGridIphItemView iphItemView) {
this.contentView = contentView;
this.iphItemView = iphItemView;
}
}
public static void bind(PropertyModel model, ViewHolder viewHolder, PropertyKey propertyKey) {
if (IPH_DIALOG_CLOSE_BUTTON_LISTENER == propertyKey) {
viewHolder.iphItemView.setupCloseIphDialogButtonOnclickListener(
model.get(IPH_DIALOG_CLOSE_BUTTON_LISTENER));
} else if (IPH_ENTRANCE_CLOSE_BUTTON_LISTENER == propertyKey) {
viewHolder.iphItemView.setupCloseIphEntranceButtonOnclickListener(
model.get(IPH_ENTRANCE_CLOSE_BUTTON_LISTENER));
} else if (IPH_ENTRANCE_SHOW_BUTTON_LISTENER == propertyKey) {
viewHolder.iphItemView.setupShowIphButtonOnclickListener(
model.get(IPH_ENTRANCE_SHOW_BUTTON_LISTENER));
} else if (IPH_SCRIM_VIEW_OBSERVER == propertyKey) {
viewHolder.iphItemView.setupIPHDialogScrimViewObserver(
model.get(IPH_SCRIM_VIEW_OBSERVER));
} else if (IS_IPH_DIALOG_VISIBLE == propertyKey) {
if (model.get(IS_IPH_DIALOG_VISIBLE)) {
viewHolder.iphItemView.showIPHDialog();
} else {
viewHolder.iphItemView.closeIphDialog();
}
} else if (IS_IPH_ENTRANCE_VISIBLE == propertyKey) {
if (model.get(IS_IPH_ENTRANCE_VISIBLE)) {
viewHolder.contentView.setupRecyclerViewFooter(viewHolder.iphItemView);
} else {
viewHolder.contentView.removeRecyclerViewFooter();
}
}
}
}
...@@ -14,6 +14,7 @@ import android.annotation.SuppressLint; ...@@ -14,6 +14,7 @@ import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
...@@ -21,7 +22,6 @@ import android.support.v7.content.res.AppCompatResources; ...@@ -21,7 +22,6 @@ import android.support.v7.content.res.AppCompatResources;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewParent; import android.view.ViewParent;
...@@ -164,6 +164,9 @@ class TabListRecyclerView extends RecyclerView { ...@@ -164,6 +164,9 @@ class TabListRecyclerView extends RecyclerView {
mDynamicView.dropCachedBitmap(); mDynamicView.dropCachedBitmap();
unregisterDynamicView(); unregisterDynamicView();
} }
if (mRecyclerViewFooter != null) {
mRecyclerViewFooter.setVisibility(VISIBLE);
}
} }
}); });
if (!animate) mFadeInAnimator.end(); if (!animate) mFadeInAnimator.end();
...@@ -336,6 +339,25 @@ class TabListRecyclerView extends RecyclerView { ...@@ -336,6 +339,25 @@ class TabListRecyclerView extends RecyclerView {
} }
} }
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
if (mRecyclerViewFooter == null || getVisibility() != View.VISIBLE) return;
// Always put the recyclerView footer below the recyclerView if there is one.
ViewHolder viewHolder = findViewHolderForAdapterPosition(getAdapter().getItemCount() - 1);
if (viewHolder == null) {
mRecyclerViewFooter.setVisibility(INVISIBLE);
} else {
if (mRecyclerViewFooter.getVisibility() != VISIBLE) {
mRecyclerViewFooter.setVisibility(VISIBLE);
}
final int padding =
(int) getResources().getDimension(R.dimen.tab_grid_iph_card_padding);
mRecyclerViewFooter.setY(
viewHolder.itemView.getBottom() + mRecyclerViewFooter.getHeight() + padding);
}
}
/** /**
* Start hiding the tab list. * Start hiding the tab list.
* @param animate Whether the visibility change should be animated. * @param animate Whether the visibility change should be animated.
...@@ -360,6 +382,9 @@ class TabListRecyclerView extends RecyclerView { ...@@ -360,6 +382,9 @@ class TabListRecyclerView extends RecyclerView {
setShadowVisibility(false); setShadowVisibility(false);
mFadeOutAnimator.start(); mFadeOutAnimator.start();
if (!animate) mFadeOutAnimator.end(); if (!animate) mFadeOutAnimator.end();
if (mRecyclerViewFooter != null) {
mRecyclerViewFooter.setVisibility(INVISIBLE);
}
} }
void postHiding() { void postHiding() {
...@@ -447,35 +472,28 @@ class TabListRecyclerView extends RecyclerView { ...@@ -447,35 +472,28 @@ class TabListRecyclerView extends RecyclerView {
} }
/** /**
* This method creates a {@link TabListMediator.IphProvider} that can show IPH for drag-and-drop * This method setup the footer of {@code recyclerView}.
* in GridTabSwitcher. * @param footer The {@link View} of the footer.
* @return The {@link TabListMediator.IphProvider} that can be used to show IPH.
* TODO(yuezhanggg): Replace this workaround with a footer itemView after we have generic list
* view adapter (crbug: 909779).
*/ */
TabListMediator.IphProvider setupIphProvider() { void setupRecyclerViewFooter(View footer) {
if (mRecyclerViewFooter != null) return;
final int height = (int) getResources().getDimension(R.dimen.tab_grid_iph_card_height); final int height = (int) getResources().getDimension(R.dimen.tab_grid_iph_card_height);
final int padding = (int) getResources().getDimension(R.dimen.tab_grid_iph_card_padding); final int padding = (int) getResources().getDimension(R.dimen.tab_grid_iph_card_padding);
// Listener to close the footer. mRecyclerViewFooter = footer;
View.OnClickListener closeListener = view -> { setPadding(0, 0, 0, height + padding);
((ViewGroup) mRecyclerViewFooter.getParent()).removeView(mRecyclerViewFooter); setScrollBarStyle(SCROLLBARS_OUTSIDE_OVERLAY);
mRecyclerViewFooter = null; mRecyclerViewFooter.setVisibility(INVISIBLE);
// Restore the recyclerview to its original state. }
setPadding(0, 0, 0, 0);
setScrollBarStyle(SCROLLBARS_INSIDE_OVERLAY);
};
return anchor -> { /**
if (getChildCount() == 0) return; * This method removes the footer of {@code recyclerView} if there is one.
if (mRecyclerViewFooter != null) return; */
TabGridIphItemView iphView = void removeRecyclerViewFooter() {
(TabGridIphItemView) LayoutInflater.from(getContext()) if (mRecyclerViewFooter == null) return;
.inflate(R.layout.iph_card_item_layout, (ViewGroup) anchor, false); ((ViewGroup) mRecyclerViewFooter.getParent()).removeView(mRecyclerViewFooter);
((ViewGroup) anchor).addView(iphView); mRecyclerViewFooter = null;
mRecyclerViewFooter = iphView; // Restore the recyclerView to its original state.
iphView.setupCloseIphEntranceButtonOnclickListener(closeListener); setPadding(0, 0, 0, 0);
setPadding(0, 0, 0, height + padding); setScrollBarStyle(SCROLLBARS_INSIDE_OVERLAY);
setScrollBarStyle(SCROLLBARS_OUTSIDE_OVERLAY);
};
} }
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment