「appbar_scrolling_view_behavior」を持つメインコンテナには実際にスクロールするのに十分なコンテンツがありませんが、AppBarLayoutのツールバーはスクロール可能であることが本当に意図されていますか?
これまでにテストしたこと:
NestedScrollView( "wrap_content"属性付き)をメインコンテナーとして使用し、TextViewを子として使用すると、AppBarLayoutは適切に機能し、スクロールしません。
ただし、いくつかのエントリと「wrap_content」属性のみを使用してRecyclerViewを使用すると(スクロールする必要がないため)、RecyclerViewがスクロールイベントを受信しなくても(OnScrollChangeListenerでテスト済み)AppBarLayoutのツールバーがスクロール可能になります)。
これが私のレイアウトコードです。
<Android.support.design.widget.CoordinatorLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
Android:id="@+id/coordinatorLayout"
Android:layout_width="match_parent"
Android:layout_height="match_parent">
<Android.support.design.widget.AppBarLayout
Android:id="@+id/appBarLayout"
Android:layout_width="match_parent"
Android:layout_height="wrap_content">
<Android.support.v7.widget.Toolbar
Android:id="@+id/toolbar"
Android:layout_width="match_parent"
Android:layout_height="?attr/actionBarSize"
Android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:theme="@style/ToolbarStyle" />
</Android.support.design.widget.AppBarLayout>
<Android.support.v7.widget.RecyclerView
Android:id="@+id/recycler"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</Android.support.design.widget.CoordinatorLayout>
ツールバーは必要ではありませんが、スクロール可能という次の効果があります。
また、すべてのRecyclerViewアイテムが表示されているかどうかを確認し、RecyclerViewのsetNestedScrollingEnabled()メソッドを使用して、これに対処する方法を見つけました。
それにもかかわらず、それは私にとって意図したとおりのバグのように見えます。意見はありますか? :D
私の現在のソリューションに興味があるかもしれない人々のために、メソッドを呼び出すときに常に-1を返すLayoutManagerのために5ミリ秒の遅延でハンドラのpostDelayed()メソッドにsetNestedScrollingEnabled()ロジックを配置する必要がありました最初と最後のアイテムが表示されるかどうか。
onStart()メソッド(RecyclerViewの初期化後)およびRecyclerViewのコンテンツ変更が発生するたびにこのコードを使用します。
final LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
//no items in the RecyclerView
if (mRecyclerView.getAdapter().getItemCount() == 0)
mRecyclerView.setNestedScrollingEnabled(false);
//if the first and the last item is visible
else if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0
&& layoutManager.findLastCompletelyVisibleItemPosition() == mRecyclerView.getAdapter().getItemCount() - 1)
mRecyclerView.setNestedScrollingEnabled(false);
else
mRecyclerView.setNestedScrollingEnabled(true);
}
}, 5);
新しいアプリを試したところ、この(意図しない)動作はサポートライブラリバージョン23.3.0(またはそれ以前)で修正されたようです。したがって、回避策はもう必要ありません!
したがって、適切な信用、この答えは私のためにほとんど解決しました https://stackoverflow.com/a/32923226/5050087 。しかし、実際にスクロール可能なリサイクラビューがあり、最後のアイテムが表示されたときにツールバーが表示されていなかったため(最初のスクロールアップではツールバーを表示しませんでした)、私はそれを変更し、より簡単な実装と動的に適応させることにしましたアダプター。
最初に、アプリバーのカスタムレイアウト動作を作成する必要があります。
public class ToolbarBehavior extends AppBarLayout.Behavior{
private boolean scrollableRecyclerView = false;
private int count;
public ToolbarBehavior() {
}
public ToolbarBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
return scrollableRecyclerView && super.onInterceptTouchEvent(parent, child, ev);
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes, int type) {
updatedScrollable(directTargetChild);
return scrollableRecyclerView && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type);
}
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
return scrollableRecyclerView && super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
private void updatedScrollable(View directTargetChild) {
if (directTargetChild instanceof RecyclerView) {
RecyclerView recyclerView = (RecyclerView) directTargetChild;
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (adapter != null) {
if (adapter.getItemCount()!= count) {
scrollableRecyclerView = false;
count = adapter.getItemCount();
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager != null) {
int lastVisibleItem = 0;
if (layoutManager instanceof LinearLayoutManager) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
lastVisibleItem = Math.abs(linearLayoutManager.findLastCompletelyVisibleItemPosition());
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
int[] lastItems = staggeredGridLayoutManager.findLastCompletelyVisibleItemPositions(new int[staggeredGridLayoutManager.getSpanCount()]);
lastVisibleItem = Math.abs(lastItems[lastItems.length - 1]);
}
scrollableRecyclerView = lastVisibleItem < count - 1;
}
}
}
} else scrollableRecyclerView = true;
}
}
次に、レイアウトファイルでappbarに対してこの動作を定義するだけです。
<Android.support.design.widget.AppBarLayout
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:fitsSystemWindows="true"
app:layout_behavior="com.yourappname.whateverdir.ToolbarBehavior"
>
画面の回転についてはテストしていないので、このように機能するかどうかを教えてください。回転が発生したときにcount変数が保存されるとは思わないので機能するはずですが、保存されない場合はお知らせください。
これは私にとって最も簡単でクリーンな実装でした。楽しんでください。
編集2:
RecyclerViewがスクロール可能でないときにツールバーがスクロール可能でないことを確認する唯一の方法は、RecyclerViewがスクロール可能であるかどうかを確認する必要があるsetScrollFlagsをプログラムで設定することです。このチェックは、アダプターが変更されるたびに実行する必要があります。
アクティビティと通信するためのインターフェース:
public interface LayoutController {
void enableScroll();
void disableScroll();
}
主な活動:
public class MainActivity extends AppCompatActivity implements
LayoutController {
private CollapsingToolbarLayout collapsingToolbarLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
collapsingToolbarLayout =
(CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
final FragmentManager manager = getSupportFragmentManager();
final Fragment fragment = new CheeseListFragment();
manager.beginTransaction()
.replace(R.id.root_content, fragment)
.commit();
}
@Override
public void enableScroll() {
final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)
collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
| AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
);
collapsingToolbarLayout.setLayoutParams(params);
}
@Override
public void disableScroll() {
final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)
collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(0);
collapsingToolbarLayout.setLayoutParams(params);
}
}
activity_main.xml:
<Android.support.v4.widget.DrawerLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:id="@+id/drawer_layout"
Android:layout_height="match_parent"
Android:layout_width="match_parent"
Android:fitsSystemWindows="true">
<Android.support.design.widget.CoordinatorLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
Android:id="@+id/main_content"
Android:layout_width="match_parent"
Android:layout_height="match_parent">
<Android.support.design.widget.AppBarLayout
Android:id="@+id/appbar"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<Android.support.design.widget.CollapsingToolbarLayout
Android:id="@+id/collapsing_toolbar"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary">
<Android.support.v7.widget.Toolbar
Android:id="@+id/toolbar"
Android:layout_width="match_parent"
Android:layout_height="?attr/actionBarSize"
Android:background="?attr/colorPrimary"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
</Android.support.design.widget.CollapsingToolbarLayout>
</Android.support.design.widget.AppBarLayout>
<FrameLayout
Android:id="@+id/root_content"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:layout_gravity="fill_vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</Android.support.design.widget.CoordinatorLayout>
</Android.support.v4.widget.DrawerLayout>
テストフラグメント:
public class CheeseListFragment extends Fragment {
private static final int DOWN = 1;
private static final int UP = 0;
private LayoutController controller;
private RecyclerView rv;
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
controller = (MainActivity) getActivity();
} catch (ClassCastException e) {
throw new RuntimeException(getActivity().getLocalClassName()
+ "must implement controller.", e);
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
rv = (RecyclerView) inflater.inflate(
R.layout.fragment_cheese_list, container, false);
setupRecyclerView(rv);
// Find out if RecyclerView are scrollable, delay required
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (rv.canScrollVertically(DOWN) || rv.canScrollVertically(UP)) {
controller.enableScroll();
} else {
controller.disableScroll();
}
}
}, 100);
return rv;
}
private void setupRecyclerView(RecyclerView recyclerView) {
final LinearLayoutManager layoutManager = new LinearLayoutManager(recyclerView.getContext());
recyclerView.setLayoutManager(layoutManager);
final SimpleStringRecyclerViewAdapter adapter =
new SimpleStringRecyclerViewAdapter(
getActivity(),
// Test ToolBar scroll
getRandomList(/* with enough items to scroll */)
// Test ToolBar pin
getRandomList(/* with only 3 items*/)
);
recyclerView.setAdapter(adapter);
}
}
ソース:
編集:
動作を制御するには、CollapsingToolbarLayoutを使用する必要があります。
ツールバーをAppBarLayoutに直接追加すると、enterAlwaysCollapsedおよびexitUntilCollapsedスクロールフラグにアクセスできますが、さまざまな要素が折りたたみにどのように反応するかを詳細に制御することはできません。 [...]セットアップはCollapsingToolbarLayoutのapp:layout_collapseMode = "pin"を使用して、ビューが折りたたまれている間、ツールバー自体が画面上部に固定されたままになるようにします。 http://Android-developers.blogspot.com。 tr/2015/05/Android-design-support-library.html
<Android.support.design.widget.CollapsingToolbarLayout
Android:layout_width="match_parent"
Android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<Android.support.v7.widget.Toolbar
Android:id="@+id/drawer_toolbar"
Android:layout_width="match_parent"
Android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"/>
</Android.support.design.widget.CollapsingToolbarLayout>
追加
app:layout_collapseMode="pin"
xmlのツールバーに。
<Android.support.v7.widget.Toolbar
Android:id="@+id/toolbar"
Android:layout_width="match_parent"
Android:layout_height="?attr/actionBarSize"
Android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:layout_collapseMode="pin"
app:theme="@style/ToolbarStyle" />
AppBarLayoutにアタッチされる可能性のある独自のBehaviorクラスを使用して実装しました。
public class CustomAppBarLayoutBehavior extends AppBarLayout.Behavior {
private RecyclerView recyclerView;
private int additionalHeight;
public CustomAppBarLayoutBehavior(RecyclerView recyclerView, int additionalHeight) {
this.recyclerView = recyclerView;
this.additionalHeight = additionalHeight;
}
public boolean isRecyclerViewScrollable(RecyclerView recyclerView) {
return recyclerView.computeHorizontalScrollRange() > recyclerView.getWidth() || recyclerView.computeVerticalScrollRange() > (recyclerView.getHeight() - additionalHeight);
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
if (isRecyclerViewScrollable(mRecyclerView)) {
return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
}
return false;
}
}
以下は、この動作を設定するコードです。
final View appBarLayout = ((DrawerActivity) getActivity()).getAppBarLayoutView();
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
layoutParams.setBehavior(new AppBarLayoutNoEmptyScrollBehavior(recyclerView, getResources().getDimensionPixelSize(R.dimen.control_bar_height)));
これはバグではなく、viewGroup内のすべてのイベントはこの方法で処理されます。 recyclerviewはcoordinatorLayoutの子であるため、イベントが生成されるたびに、最初に親がチェックされ、親のみが関心を持たない場合は子に渡されます。グーグルを参照してください ドキュメント
LayoutManager
サブクラスのこのようなものは、望ましい動作をもたらすようです:
_@Override
public boolean canScrollVertically() {
int firstCompletelyVisibleItemPosition = findFirstCompletelyVisibleItemPosition();
if (firstCompletelyVisibleItemPosition == RecyclerView.NO_POSITION) return false;
int lastCompletelyVisibleItemPosition = findLastCompletelyVisibleItemPosition();
if (lastCompletelyVisibleItemPosition == RecyclerView.NO_POSITION) return false;
if (firstCompletelyVisibleItemPosition == 0 &&
lastCompletelyVisibleItemPosition == getItemCount() - 1)
return false;
return super.canScrollVertically();
}
_
canScrollVertically()
のドキュメントには次のように書かれています:
_/**
* Query if vertical scrolling is currently supported. The default implementation
* returns false.
*
* @return True if this LayoutManager can scroll the current contents vertically
*/
_
「現在の内容垂直にスクロールできます」という文言に注意してください。これは、現在の状態が戻り値に反映されるべきだと思います。
しかし、それはv7 recyclerviewライブラリ(23.1.1)を通じて提供されるLayoutManager
サブクラスのいずれによっても行われません。それが正しい解決策であるかどうかは少しheします。この質問で説明した状況以外の状況で望ましくない影響を引き起こす可能性があります。
this 設計ライブラリ要素をサポートするためのサンプルを試すことをお勧めします。
これは、サンプルのレイアウトのようなレイアウトです。
<Android.support.design.widget.CoordinatorLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
Android:id="@+id/main_content"
Android:layout_width="match_parent"
Android:layout_height="match_parent">
<Android.support.design.widget.AppBarLayout
Android:id="@+id/appbar"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<Android.support.v7.widget.Toolbar
Android:id="@+id/toolbar"
Android:layout_width="match_parent"
Android:layout_height="?attr/actionBarSize"
Android:background="?attr/colorPrimary"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:layout_scrollFlags="scroll|enterAlways" />
<Android.support.design.widget.TabLayout
Android:id="@+id/tabs"
Android:layout_width="match_parent"
Android:layout_height="wrap_content" />
</Android.support.design.widget.AppBarLayout>
<Android.support.v4.view.ViewPager
Android:id="@+id/viewpager"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</Android.support.design.widget.CoordinatorLayout>
おかげで、RecyclerViewのカスタムクラスを作成しましたが、キーはまだsetNestedScrollingEnabled()
を使用しています。私の側ではうまくいきました。
public class RecyclerViewCustom extends RecyclerView implements ViewTreeObserver.OnGlobalLayoutListener
{
public RecyclerViewCustom(Context context)
{
super(context);
}
public RecyclerViewCustom(Context context, @Nullable AttributeSet attrs)
{
super(context, attrs);
}
public RecyclerViewCustom(Context context, @Nullable AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
/**
* This supports scrolling when using RecyclerView with AppbarLayout
* Basically RecyclerView should not be scrollable when there's no data or the last item is visible
*
* Call this method after Adapter#updateData() get called
*/
public void addOnGlobalLayoutListener()
{
this.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
@Override
public void onGlobalLayout()
{
// If the last item is visible or there's no data, the RecyclerView should not be scrollable
RecyclerView.LayoutManager layoutManager = getLayoutManager();
final RecyclerView.Adapter adapter = getAdapter();
if (adapter == null || adapter.getItemCount() <= 0 || layoutManager == null)
{
setNestedScrollingEnabled(false);
}
else
{
int lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();
boolean isLastItemVisible = lastVisibleItemPosition == adapter.getItemCount() - 1;
setNestedScrollingEnabled(!isLastItemVisible);
}
unregisterGlobalLayoutListener();
}
private void unregisterGlobalLayoutListener()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
{
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
else
{
getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
}
ser3623735の回答に少し追加したいと思います。次のコードは絶対に間違っています。
// Find out if RecyclerView are scrollable, delay required
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (rv.canScrollVertically(DOWN) || rv.canScrollVertically(UP)) {
controller.enableScroll();
} else {
controller.disableScroll();
}
}
}, 100);
そして、それが機能する場合でも-すべてのケースをカバーするわけではありません。データが100ミリ秒で表示されるという保証はまったくありません。また、onCreateViewメソッドだけでなく、データを使用するプロセスでデータがビューの高さを引き伸ばすことができます。そのため、次のコードを使用して、ビューの高さの変更を追跡する必要があります。
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if(bottom != oldBottom)
{
mActivity.setScrollEnabled(view.canScrollVertically(0) || view.canScrollVertically(1));
}
}
});
さらに、スクロール状態を制御するために2つの別々のメソッドを作成する必要はありません。1つのsetScrollEnabledメソッドを使用する必要があります。
public void setScrollEnabled(boolean enabled) {
final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams)
mToolbar.getLayoutParams();
params.setScrollFlags(enabled ?
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS : 0);
mToolbar.setLayoutParams(params);
}