この質問は以前、広すぎて不明確な方法で尋ねられました here なので、私はそれをはるかに具体的にしました。私が試したことの完全な説明とコード。
Googleカレンダーの上部にビューがある方法を模倣する必要があります。これにより、下部のビューをアニメーション化してプッシュダウンできますが、動作が異なります。私がやろうとしていることを3つの特徴について要約しました。
Googleカレンダーアプリは次のようになります。
下部のビューをスクロールすると、上部のビューもゆっくりと非表示になります。
過去に見つけたさまざまなソリューションを使用して、必要な動作の一部のみを実装することに成功しました。
ツールバーにUIを表示するには、矢印ビューなどのビューをいくつか表示します。手動で展開/折りたたみするには、setExpanded
ビューでAppBarLayout
を使用します。矢印の回転には、AppBarLayout
を使用して、addOnOffsetChangedListener
のサイズが変更されたリスナーを使用します。
スナップは、snap
の値をCollapsingToolbarLayout
のlayout_scrollFlags
属性に追加することで簡単に実行できます。しかし、それを本当にうまく機能させるために、奇妙な問題(報告された ここ )なしで、私は このソリューション 。
スクロール時に上面図に影響を与えることをブロックするには、#2で使用したのと同じコードを使用します( here )、そこでsetExpandEnabled
を呼び出すことによって。これは、上面図が折りたたまれている場合に正常に機能します。
#3と同様ですが、残念ながら、両方向にあるsetNestedScrollingEnabled
を使用するため、これは上面図が折りたたまれている場合にのみうまく機能します。展開しても、カレンダーアプリとは対照的に、底面ビューを上にスクロールできます。展開するときは、実際にスクロールすることなく、折りたたむことだけを許可する必要があります。
これが良い点と悪い点のデモンストレーションです。
要するに、私は1-3で成功しましたが、4-5では成功しませんでした。
これが現在のコードです(プロジェクト全体としても利用可能です here ):
ScrollingActivity.kt
class ScrollingActivity : AppCompatActivity(), AppBarTracking {
private var mNestedView: MyRecyclerView? = null
private var mAppBarOffset: Int = 0
private var mAppBarIdle = false
private var mAppBarMaxOffset: Int = 0
private var isExpanded: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scrolling)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
mNestedView = findViewById(R.id.nestedView)
app_bar.addOnOffsetChangedListener({ appBarLayout, verticalOffset ->
mAppBarOffset = verticalOffset
val totalScrollRange = appBarLayout.totalScrollRange
val progress = (-verticalOffset).toFloat() / totalScrollRange
arrowImageView.rotation = 180 + progress * 180
isExpanded = verticalOffset == 0;
mAppBarIdle = mAppBarOffset >= 0 || mAppBarOffset <= mAppBarMaxOffset
if (mAppBarIdle)
setExpandAndCollapseEnabled(isExpanded)
})
app_bar.post(Runnable { mAppBarMaxOffset = -app_bar.totalScrollRange })
mNestedView!!.setAppBarTracking(this)
mNestedView!!.layoutManager = LinearLayoutManager(this)
mNestedView!!.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun getItemCount(): Int = 100
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return object : ViewHolder(LayoutInflater.from(parent.context).inflate(Android.R.layout.simple_list_item_1, parent, false)) {}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
(holder.itemView.findViewById<View>(Android.R.id.text1) as TextView).text = "item $position"
}
}
expandCollapseButton.setOnClickListener({ v ->
isExpanded = !isExpanded
app_bar.setExpanded(isExpanded, true)
})
}
private fun setExpandAndCollapseEnabled(enabled: Boolean) {
mNestedView!!.isNestedScrollingEnabled = enabled
}
override fun isAppBarExpanded(): Boolean = mAppBarOffset == 0
override fun isAppBarIdle(): Boolean = mAppBarIdle
}
MyRecyclerView.kt
/**A RecyclerView that allows temporary pausing of casuing its scroll to affect appBarLayout, based on https://stackoverflow.com/a/45338791/878126 */
class MyRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : RecyclerView(context, attrs, defStyle) {
private var mAppBarTracking: AppBarTracking? = null
private var mView: View? = null
private var mTopPos: Int = 0
private var mLayoutManager: LinearLayoutManager? = null
interface AppBarTracking {
fun isAppBarIdle(): Boolean
fun isAppBarExpanded(): Boolean
}
override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?,
type: Int): Boolean {
if (type == ViewCompat.TYPE_NON_TOUCH && mAppBarTracking!!.isAppBarIdle()
&& isNestedScrollingEnabled) {
if (dy > 0) {
if (mAppBarTracking!!.isAppBarExpanded()) {
consumed!![1] = dy
return true
}
} else {
mTopPos = mLayoutManager!!.findFirstVisibleItemPosition()
if (mTopPos == 0) {
mView = mLayoutManager!!.findViewByPosition(mTopPos)
if (-mView!!.top + dy <= 0) {
consumed!![1] = dy - mView!!.top
return true
}
}
}
}
val returnValue = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type)
if (offsetInWindow != null && !isNestedScrollingEnabled && offsetInWindow[1] != 0)
offsetInWindow[1] = 0
return returnValue
}
override fun setLayoutManager(layout: RecyclerView.LayoutManager) {
super.setLayoutManager(layout)
mLayoutManager = layoutManager as LinearLayoutManager
}
fun setAppBarTracking(appBarTracking: AppBarTracking) {
mAppBarTracking = appBarTracking
}
}
ScrollingCalendarBehavior.kt
class ScrollingCalendarBehavior(context: Context, attrs: AttributeSet) : AppBarLayout.Behavior(context, attrs) {
override fun onInterceptTouchEvent(parent: CoordinatorLayout?, child: AppBarLayout?, ev: MotionEvent): Boolean = false
}
activity_scrolling.xml
<Android.support.design.widget.CoordinatorLayout
Android:id="@+id/coordinatorLayout" xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto" xmlns:tools="http://schemas.Android.com/tools"
Android:layout_width="match_parent" Android:layout_height="match_parent" tools:context=".ScrollingActivity">
<Android.support.design.widget.AppBarLayout
Android:id="@+id/app_bar" Android:layout_width="match_parent" Android:layout_height="wrap_content"
Android:fitsSystemWindows="true" Android:stateListAnimator="@null" Android:theme="@style/AppTheme.AppBarOverlay"
app:expanded="false" app:layout_behavior="com.example.user.expandingtopviewtest.ScrollingCalendarBehavior"
tools:targetApi="Lollipop">
<Android.support.design.widget.CollapsingToolbarLayout
Android:id="@+id/collapsingToolbarLayout" Android:layout_width="match_parent"
Android:layout_height="match_parent" Android:fitsSystemWindows="true"
Android:minHeight="?attr/actionBarSize" app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap" app:statusBarScrim="?attr/colorPrimaryDark">
<LinearLayout
Android:layout_width="match_parent" Android:layout_height="250dp"
Android:layout_marginTop="?attr/actionBarSize" app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="1.0">
<TextView
Android:layout_width="match_parent" Android:layout_height="match_parent" Android:paddingLeft="10dp"
Android:paddingRight="10dp" Android:text="some large, expanded view"/>
</LinearLayout>
<Android.support.v7.widget.Toolbar
Android:id="@+id/toolbar" Android:layout_width="match_parent"
Android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay">
<Android.support.constraint.ConstraintLayout
Android:id="@+id/expandCollapseButton" Android:layout_width="match_parent"
Android:layout_height="?attr/actionBarSize" Android:background="?android:selectableItemBackground"
Android:clickable="true" Android:focusable="true" Android:orientation="vertical">
<TextView
Android:id="@+id/titleTextView" Android:layout_width="wrap_content"
Android:layout_height="wrap_content" Android:layout_marginBottom="8dp"
Android:layout_marginLeft="8dp" Android:layout_marginStart="8dp" Android:ellipsize="end"
Android:gravity="center" Android:maxLines="1" Android:text="title"
Android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
Android:textColor="@Android:color/white" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<ImageView
Android:id="@+id/arrowImageView" Android:layout_width="wrap_content" Android:layout_height="0dp"
Android:layout_marginLeft="8dp" Android:layout_marginStart="8dp"
app:layout_constraintBottom_toBottomOf="@+id/titleTextView"
app:layout_constraintStart_toEndOf="@+id/titleTextView"
app:layout_constraintTop_toTopOf="@+id/titleTextView"
app:srcCompat="@Android:drawable/arrow_down_float"
tools:ignore="ContentDescription,RtlHardcoded"/>
</Android.support.constraint.ConstraintLayout>
</Android.support.v7.widget.Toolbar>
</Android.support.design.widget.CollapsingToolbarLayout>
</Android.support.design.widget.AppBarLayout>
<com.example.user.expandingtopviewtest.MyRecyclerView
Android:id="@+id/nestedView" Android:layout_width="match_parent" Android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".ScrollingActivity"/>
</Android.support.design.widget.CoordinatorLayout>
上面図を拡大したときにスクロールをブロックし、スクロール中に折りたたむことができるようにするにはどうすればよいですか?
完全に消えるのではなく、折りたたんだときに上面図を小さい方のビューに置き換える(展開すると大きいビューに戻す)にはどうすればよいですか?
私が尋ねたことの基本はわかっていますが、現在のコードにはまだ2つの問題があります(Githubで入手可能、 here ) ):
Android:background="?attr/selectableItemBackgroundBorderless"
を使用し、展開中にこの領域をクリックすると、小さなビューでクリックが行われます。小さなビューを別のツールバーに配置して処理しましたが、クリック効果がまったく表示されません。私はこれについて書いた ここ 、サンプルプロジェクトを含む。修正は次のとおりです。
<Android.support.design.widget.CoordinatorLayout
Android:id="@+id/coordinatorLayout" xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto" xmlns:tools="http://schemas.Android.com/tools"
Android:layout_width="match_parent" Android:layout_height="match_parent" tools:context=".MainActivity">
<Android.support.design.widget.AppBarLayout
Android:id="@+id/app_bar" Android:layout_width="match_parent" Android:layout_height="wrap_content"
Android:fitsSystemWindows="true" Android:stateListAnimator="@null" Android:theme="@style/AppTheme.AppBarOverlay"
app:expanded="false" app:layout_behavior="com.example.expandedtopviewtestupdate.ScrollingCalendarBehavior"
tools:targetApi="Lollipop">
<Android.support.design.widget.CollapsingToolbarLayout
Android:id="@+id/collapsingToolbarLayout" Android:layout_width="match_parent"
Android:layout_height="match_parent" Android:clipChildren="false" Android:clipToPadding="false"
Android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways"
app:statusBarScrim="?attr/colorPrimaryDark">
<!--large view -->
<LinearLayout
Android:id="@+id/largeView" Android:layout_width="match_parent" Android:layout_height="280dp"
Android:layout_marginTop="?attr/actionBarSize" Android:orientation="vertical"
app:layout_collapseMode="parallax" app:layout_collapseParallaxMultiplier="1.0">
<TextView
Android:id="@+id/largeTextView" Android:layout_width="match_parent"
Android:layout_height="match_parent" Android:layout_gravity="center"
Android:background="?attr/selectableItemBackgroundBorderless" Android:clickable="true"
Android:focusable="true" Android:focusableInTouchMode="false" Android:gravity="center"
Android:text="largeView" Android:textSize="14dp" tools:background="?attr/colorPrimary"
tools:layout_gravity="top|center_horizontal" tools:layout_height="40dp" tools:layout_width="40dp"
tools:text="1"/>
</LinearLayout>
<!--top toolbar-->
<Android.support.v7.widget.Toolbar
Android:id="@+id/toolbar" Android:layout_width="match_parent" Android:layout_height="wrap_content"
Android:layout_marginBottom="@dimen/small_view_height" app:contentInsetStart="0dp"
app:layout_collapseMode="pin" app:popupTheme="@style/AppTheme.PopupOverlay">
<Android.support.constraint.ConstraintLayout
Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:clickable="true"
Android:focusable="true">
<LinearLayout
Android:id="@+id/expandCollapseButton" Android:layout_width="match_parent"
Android:layout_height="?attr/actionBarSize"
Android:background="?android:selectableItemBackground" Android:gravity="center_vertical"
Android:orientation="horizontal" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
Android:id="@+id/titleTextView" Android:layout_width="wrap_content"
Android:layout_height="wrap_content" Android:ellipsize="end" Android:gravity="center"
Android:maxLines="1" Android:text="title"
Android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
Android:textColor="@Android:color/white"/>
<ImageView
Android:id="@+id/arrowImageView" Android:layout_width="wrap_content"
Android:layout_height="wrap_content" Android:layout_marginLeft="8dp"
Android:layout_marginStart="8dp" app:srcCompat="@Android:drawable/arrow_up_float"
tools:ignore="ContentDescription,RtlHardcoded"/>
</LinearLayout>
</Android.support.constraint.ConstraintLayout>
</Android.support.v7.widget.Toolbar>
<Android.support.v7.widget.Toolbar
Android:id="@+id/smallLayoutContainer" Android:layout_width="match_parent"
Android:layout_height="wrap_content" Android:layout_marginTop="?attr/actionBarSize"
Android:clipChildren="false" Android:clipToPadding="false" app:contentInsetStart="0dp"
app:layout_collapseMode="pin">
<!--small view-->
<LinearLayout
Android:id="@+id/smallLayout" Android:layout_width="match_parent"
Android:layout_height="@dimen/small_view_height" Android:clipChildren="false"
Android:clipToPadding="false" Android:orientation="horizontal" tools:background="#ff330000"
tools:layout_height="@dimen/small_view_height">
<TextView
Android:id="@+id/smallTextView" Android:layout_width="match_parent"
Android:layout_height="match_parent" Android:layout_gravity="center"
Android:background="?attr/selectableItemBackgroundBorderless" Android:clickable="true"
Android:focusable="true" Android:focusableInTouchMode="false" Android:gravity="center"
Android:text="smallView" Android:textSize="14dp" tools:background="?attr/colorPrimary"
tools:layout_gravity="top|center_horizontal" tools:layout_height="40dp"
tools:layout_width="40dp" tools:text="1"/>
</LinearLayout>
</Android.support.v7.widget.Toolbar>
</Android.support.design.widget.CollapsingToolbarLayout>
</Android.support.design.widget.AppBarLayout>
<com.example.expandedtopviewtestupdate.MyRecyclerView
Android:id="@+id/nestedView" Android:layout_width="match_parent" Android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".ScrollingActivity"/>
</Android.support.design.widget.CoordinatorLayout>
注:完全に更新されたプロジェクトが利用可能です here 。
上面図を拡大したときにスクロールをブロックし、スクロール中に折りたたむことができるようにするにはどうすればよいですか?
問題#1:アプリバーが折りたたまれていない場合、RecyclerView
はまったくスクロールできないはずです。これを修正するには、次のようにenterAlways
のスクロールフラグにCollapsingToolbarLayout
を追加します。
_<Android.support.design.widget.CollapsingToolbarLayout
Android:id="@+id/collapsingToolbarLayout"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:clipChildren="false"
Android:clipToPadding="false"
Android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways"
app:statusBarScrim="?attr/colorPrimaryDark">
_
enterAlways
は、その機能を抑制しているため、閉じたときにアプリバーを開きませんが、それ以外の場合は希望どおりに機能します。
問題#2:アプリバーが完全に展開されている場合、RecyclerView
を上にスクロールできないようにする必要があります。これはたまたま問題#1とは別の問題です。
[更新]これを修正するには、RecyclerView
が上にスクロールしようとしたときに、アプリバーがスクロールを消費するようにRecyclerView
の動作を変更します。完全に展開されるか、scroll(dy
)が消費された後に完全に展開されます。 RecyclerView
は上にスクロールできますが、その動作SlidingPanelBehavior
がスクロールを消費するため、そのアクションは表示されません。アプリバーが完全に展開されていないが、現在のスクロールが消費された後に展開される場合、動作は、スクロールを完全に消費する前に、modification dy
を呼び出し、スーパーを呼び出すことによって、アプリバーを完全に展開します。 (SlidingPanelBehavior#onNestedPreScroll()
を参照)。 (前の回答では、appBarの動作が変更されました。動作の変更をRecyclerView
に配置することをお勧めします。)
問題#3:ネストされたスクロールがすでに必要な状態にあるときにRecyclerView
のネストされたスクロールを有効/無効に設定すると、問題が発生します。これらの問題を回避するには、ScrollingActivity
の次のコード変更で実際に変更が行われている場合にのみ、ネストされたスクロールの状態を変更します。
_private void setExpandAndCollapseEnabled(boolean enabled) {
if (mNestedView.isNestedScrollingEnabled() != enabled) {
mNestedView.setNestedScrollingEnabled(enabled);
}
}
_
これは、テストアプリが上記からの変更でどのように動作するかです。
上記の変更で変更されたモジュールは、投稿の最後にあります。
完全に消えるのではなく、折りたたんだときに上面図を小さい方のビューに置き換える(展開すると大きいビューに戻す)にはどうすればよいですか?
[更新]小さいビューをCollapsingToolbarLayout
の直接の子にして、Toolbar
の兄弟になるようにします。以下は、このアプローチのデモンストレーションです。小さい方のビューのcollapseMode
はpin
に設定されます。小さい方のビューの余白とツールバーの余白が調整され、小さい方のビューがツールバーのすぐ下に表示されます。 CollapsingToolbarLayout
はFrameLayout
であるため、ビューはスタックし、FrameLayout
の高さは最も高い子ビューの高さになります。 この構造により、インセットの調整が必要な問題やクリック効果の欠落の問題が回避されます。
最後の問題が1つ残っており、小さい方のビューを下にドラッグしてもアプリバーが開かないことを前提として、アプリバーを下にドラッグすると開くはずです。ドラッグ時にアプリバーを開くことを許可するには、 setDragCallback
of _AppBarLayout.Behavior
_を使用します。小さい方のビューがappBarに組み込まれているため、下にドラッグするとappbarが開きます。これを防ぐために、MyAppBarBehavior
と呼ばれる新しい動作がappbarに付加されます。この動作は、MainActivity
のコードと組み合わせて、小さいビューをドラッグしてアプリバーを開くことを防ぎますが、ツールバーをドラッグすることはできます。
activity_main.xml
_<Android.support.design.widget.CoordinatorLayout
Android:id="@+id/coordinatorLayout"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
tools:context=".MainActivity">
<Android.support.design.widget.AppBarLayout
Android:id="@+id/app_bar"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:fitsSystemWindows="true"
Android:stateListAnimator="@null"
Android:theme="@style/AppTheme.AppBarOverlay"
app:expanded="false"
app:layout_behavior=".MyAppBarBehavior"
tools:targetApi="Lollipop">
<Android.support.design.widget.CollapsingToolbarLayout
Android:id="@+id/collapsingToolbarLayout"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:clipChildren="false"
Android:clipToPadding="false"
Android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways"
app:statusBarScrim="?attr/colorPrimaryDark">
<!--large view -->
<LinearLayout
Android:id="@+id/largeView"
Android:layout_width="match_parent"
Android:layout_height="280dp"
Android:layout_marginTop="?attr/actionBarSize"
Android:orientation="vertical"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="1.0">
<TextView
Android:id="@+id/largeTextView"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:layout_gravity="center"
Android:background="?attr/selectableItemBackgroundBorderless"
Android:clickable="true"
Android:focusable="true"
Android:focusableInTouchMode="false"
Android:gravity="center"
Android:text="largeView"
Android:textSize="14dp"
tools:background="?attr/colorPrimary"
tools:layout_gravity="top|center_horizontal"
tools:layout_height="40dp"
tools:layout_width="40dp"
tools:text="1" />
</LinearLayout>
<!--top toolbar-->
<Android.support.v7.widget.Toolbar
Android:id="@+id/toolbar"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:layout_marginBottom="@dimen/small_view_height"
app:contentInsetStart="0dp"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay">
<Android.support.constraint.ConstraintLayout
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:clickable="true"
Android:focusable="true">
<LinearLayout
Android:id="@+id/expandCollapseButton"
Android:layout_width="match_parent"
Android:layout_height="?attr/actionBarSize"
Android:background="?android:selectableItemBackground"
Android:gravity="center_vertical"
Android:orientation="horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
Android:id="@+id/titleTextView"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:ellipsize="end"
Android:gravity="center"
Android:maxLines="1"
Android:text="title"
Android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
Android:textColor="@Android:color/white" />
<ImageView
Android:id="@+id/arrowImageView"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:layout_marginLeft="8dp"
Android:layout_marginStart="8dp"
app:srcCompat="@Android:drawable/arrow_up_float"
tools:ignore="ContentDescription,RtlHardcoded" />
</LinearLayout>
</Android.support.constraint.ConstraintLayout>
</Android.support.v7.widget.Toolbar>
<!--small view-->
<LinearLayout
Android:id="@+id/smallLayout"
Android:layout_width="match_parent"
Android:layout_height="@dimen/small_view_height"
Android:layout_marginTop="?attr/actionBarSize"
Android:clipChildren="false"
Android:clipToPadding="false"
Android:orientation="horizontal"
app:layout_collapseMode="pin"
tools:background="#ff330000"
tools:layout_height="@dimen/small_view_height">
<TextView
Android:id="@+id/smallTextView"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:layout_gravity="center"
Android:background="?attr/selectableItemBackgroundBorderless"
Android:clickable="true"
Android:focusable="true"
Android:focusableInTouchMode="false"
Android:gravity="center"
Android:text="smallView"
Android:textSize="14dp"
tools:background="?attr/colorPrimary"
tools:layout_gravity="top|center_horizontal"
tools:layout_height="40dp"
tools:layout_width="40dp"
tools:text="1" />
</LinearLayout>
</Android.support.design.widget.CollapsingToolbarLayout>
</Android.support.design.widget.AppBarLayout>
<com.example.expandedtopviewtestupdate.MyRecyclerView
Android:id="@+id/nestedView"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".SlidingPanelBehavior" />
</Android.support.design.widget.CoordinatorLayout>
_
最後に、addOnOffsetChangedListener
に次のコードを追加して、アプリバーが拡大および縮小したときに小さいビューでフェードアウト/フェードアウトします。ビューのアルファがゼロ(非表示)になったら、その可視性を_View.INVISIBLE
_に設定して、クリックできないようにします。ビューのアルファがゼロを超えたら、可視性を_View.VISIBLE
_に設定して、ビューを表示およびクリック可能にします。
_mSmallLayout.setAlpha((float) -verticalOffset / totalScrollRange);
// If the small layout is not visible, make it officially invisible so
// it can't receive clicks.
if (alpha == 0) {
mSmallLayout.setVisibility(View.INVISIBLE);
} else if (mSmallLayout.getVisibility() == View.INVISIBLE) {
mSmallLayout.setVisibility(View.VISIBLE);
}
_
結果は次のとおりです。
上記のすべての変更が組み込まれた新しいモジュールを次に示します。
MainActivity.Java
_public class MainActivity extends AppCompatActivity
implements MyRecyclerView.AppBarTracking {
private MyRecyclerView mNestedView;
private int mAppBarOffset = 0;
private boolean mAppBarIdle = true;
private int mAppBarMaxOffset = 0;
private AppBarLayout mAppBar;
private boolean mIsExpanded = false;
private ImageView mArrowImageView;
private LinearLayout mSmallLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
LinearLayout expandCollapse;
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
expandCollapse = findViewById(R.id.expandCollapseButton);
mArrowImageView = findViewById(R.id.arrowImageView);
mNestedView = findViewById(R.id.nestedView);
mAppBar = findViewById(R.id.app_bar);
mSmallLayout = findViewById(R.id.smallLayout);
// Log when the small text view is clicked
findViewById(R.id.smallTextView).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "<<<<click small layout");
}
});
// Log when the big text view is clicked.
findViewById(R.id.largeTextView).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "<<<<click big view");
}
});
setSupportActionBar(toolbar);
ActionBar ab = getSupportActionBar();
if (ab != null) {
getSupportActionBar().setDisplayShowTitleEnabled(false);
}
mAppBar.post(new Runnable() {
@Override
public void run() {
mAppBarMaxOffset = -mAppBar.getTotalScrollRange();
CoordinatorLayout.LayoutParams lp =
(CoordinatorLayout.LayoutParams) mAppBar.getLayoutParams();
MyAppBarBehavior behavior = (MyAppBarBehavior) lp.getBehavior();
// Only allow drag-to-open if the drag touch is on the toolbar.
// Once open, all drags are allowed.
if (behavior != null) {
behavior.setCanOpenBottom(findViewById(R.id.toolbar).getHeight());
}
}
});
mNestedView.setAppBarTracking(this);
mNestedView.setLayoutManager(new LinearLayoutManager(this));
mNestedView.setAdapter(new RecyclerView.Adapter<RecyclerView.ViewHolder>() {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(
LayoutInflater.from(parent.getContext())
.inflate(Android.R.layout.simple_list_item_1, parent, false));
}
@SuppressLint("SetTextI18n")
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((TextView) holder.itemView.findViewById(Android.R.id.text1))
.setText("Item " + position);
}
@Override
public int getItemCount() {
return 200;
}
class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View view) {
super(view);
}
}
});
mAppBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
mAppBarOffset = verticalOffset;
int totalScrollRange = appBarLayout.getTotalScrollRange();
float progress = (float) (-verticalOffset) / (float) totalScrollRange;
mArrowImageView.setRotation(-progress * 180);
mIsExpanded = verticalOffset == 0;
mAppBarIdle = mAppBarOffset >= 0 || mAppBarOffset <= mAppBarMaxOffset;
float alpha = (float) -verticalOffset / totalScrollRange;
mSmallLayout.setAlpha(alpha);
// If the small layout is not visible, make it officially invisible so
// it can't receive clicks.
if (alpha == 0) {
mSmallLayout.setVisibility(View.INVISIBLE);
} else if (mSmallLayout.getVisibility() == View.INVISIBLE) {
mSmallLayout.setVisibility(View.VISIBLE);
}
}
});
expandCollapse.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setExpandAndCollapseEnabled(true);
if (mIsExpanded) {
setExpandAndCollapseEnabled(false);
}
mIsExpanded = !mIsExpanded;
mNestedView.stopScroll();
mAppBar.setExpanded(mIsExpanded, true);
}
});
}
private void setExpandAndCollapseEnabled(boolean enabled) {
if (mNestedView.isNestedScrollingEnabled() != enabled) {
mNestedView.setNestedScrollingEnabled(enabled);
}
}
@Override
public boolean isAppBarExpanded() {
return mAppBarOffset == 0;
}
@Override
public boolean isAppBarIdle() {
return mAppBarIdle;
}
private static final String TAG = "MainActivity";
}
_
SlidingPanelBehavior.Java
_public class SlidingPanelBehavior extends AppBarLayout.ScrollingViewBehavior {
private AppBarLayout mAppBar;
public SlidingPanelBehavior() {
super();
}
public SlidingPanelBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(final CoordinatorLayout parent, View child, View dependency) {
if (mAppBar == null && dependency instanceof AppBarLayout) {
// Capture our appbar for later use.
mAppBar = (AppBarLayout) dependency;
}
return dependency instanceof AppBarLayout;
}
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent event) {
int action = event.getAction();
if (event.getAction() != MotionEvent.ACTION_DOWN) { // Only want "down" events
return false;
}
if (getAppBarLayoutOffset(mAppBar) == -mAppBar.getTotalScrollRange()) {
// When appbar is collapsed, don't let it open through nested scrolling.
setNestedScrollingEnabledWithTest((NestedScrollingChild2) child, false);
} else {
// Appbar is partially to fully expanded. Set nested scrolling enabled to activate
// the methods within this behavior.
setNestedScrollingEnabledWithTest((NestedScrollingChild2) child, true);
}
return false;
}
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child,
@NonNull View directTargetChild, @NonNull View target,
int axes, int type) {
//noinspection RedundantCast
return ((NestedScrollingChild2) child).isNestedScrollingEnabled();
}
@Override
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child,
@NonNull View target, int dx, int dy, @NonNull int[] consumed,
int type) {
// How many pixels we must scroll to fully expand the appbar. This value is <= 0.
final int appBarOffset = getAppBarLayoutOffset(mAppBar);
// Check to see if this scroll will expand the appbar 100% or collapse it fully.
if (dy <= appBarOffset) {
// Scroll by the amount that will fully expand the appbar and dispose of the rest (dy).
super.onNestedPreScroll(coordinatorLayout, mAppBar, target, dx,
appBarOffset, consumed, type);
consumed[1] += dy;
} else if (dy >= (mAppBar.getTotalScrollRange() + appBarOffset)) {
// This scroll will collapse the appbar. Collapse it and dispose of the rest.
super.onNestedPreScroll(coordinatorLayout, mAppBar, target, dx,
mAppBar.getTotalScrollRange() + appBarOffset,
consumed, type);
consumed[1] += dy;
} else {
// This scroll will leave the appbar partially open. Just do normal stuff.
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
}
}
/**
* {@code onNestedPreFling()} is overriden to address a nested scrolling defect that was
* introduced in API 26. This method prevent the appbar from misbehaving when scrolled/flung.
* <p>
* Refer to <a href="https://issuetracker.google.com/issues/65448468" target="_blank">"Bug in design support library"</a>
*/
@Override
public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout,
@NonNull View child, @NonNull View target,
float velocityX, float velocityY) {
//noinspection RedundantCast
if (((NestedScrollingChild2) child).isNestedScrollingEnabled()) {
// Just stop the nested fling and let the appbar settle into place.
((NestedScrollingChild2) child).stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
return true;
}
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
private static int getAppBarLayoutOffset(AppBarLayout appBar) {
final CoordinatorLayout.Behavior behavior =
((CoordinatorLayout.LayoutParams) appBar.getLayoutParams()).getBehavior();
if (behavior instanceof AppBarLayout.Behavior) {
return ((AppBarLayout.Behavior) behavior).getTopAndBottomOffset();
}
return 0;
}
// Something goes amiss when the flag it set to its current value, so only call
// setNestedScrollingEnabled() if it will result in a change.
private void setNestedScrollingEnabledWithTest(NestedScrollingChild2 child, boolean enabled) {
if (child.isNestedScrollingEnabled() != enabled) {
child.setNestedScrollingEnabled(enabled);
}
}
@SuppressWarnings("unused")
private static final String TAG = "SlidingPanelBehavior";
}
_
MyRecyclerView.kt
_/**A RecyclerView that allows temporary pausing of casuing its scroll to affect appBarLayout, based on https://stackoverflow.com/a/45338791/878126 */
class MyRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : RecyclerView(context, attrs, defStyle) {
private var mAppBarTracking: AppBarTracking? = null
private var mView: View? = null
private var mTopPos: Int = 0
private var mLayoutManager: LinearLayoutManager? = null
interface AppBarTracking {
fun isAppBarIdle(): Boolean
fun isAppBarExpanded(): Boolean
}
override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?, type: Int): Boolean {
if (type == ViewCompat.TYPE_NON_TOUCH && mAppBarTracking!!.isAppBarIdle()
&& isNestedScrollingEnabled) {
if (dy > 0) {
if (mAppBarTracking!!.isAppBarExpanded()) {
consumed!![1] = dy
return true
}
} else {
mTopPos = mLayoutManager!!.findFirstVisibleItemPosition()
if (mTopPos == 0) {
mView = mLayoutManager!!.findViewByPosition(mTopPos)
if (-mView!!.top + dy <= 0) {
consumed!![1] = dy - mView!!.top
return true
}
}
}
}
if (dy < 0 && type == ViewCompat.TYPE_TOUCH && mAppBarTracking!!.isAppBarExpanded()) {
consumed!![1] = dy
return true
}
val returnValue = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type)
if (offsetInWindow != null && !isNestedScrollingEnabled && offsetInWindow[1] != 0)
offsetInWindow[1] = 0
return returnValue
}
override fun setLayoutManager(layout: RecyclerView.LayoutManager) {
super.setLayoutManager(layout)
mLayoutManager = layoutManager as LinearLayoutManager
}
fun setAppBarTracking(appBarTracking: AppBarTracking) {
mAppBarTracking = appBarTracking
}
override fun fling(velocityX: Int, velocityY: Int): Boolean {
var velocityY = velocityY
if (!mAppBarTracking!!.isAppBarIdle()) {
val vc = ViewConfiguration.get(context)
velocityY = if (velocityY < 0) -vc.scaledMinimumFlingVelocity
else vc.scaledMinimumFlingVelocity
}
return super.fling(velocityX, velocityY)
}
}
_
MyAppBarBehavior.Java
_/**
* Attach this behavior to AppBarLayout to disable the bottom portion of a closed appBar
* so it cannot be touched to open the appBar. This behavior is helpful if there is some
* portion of the appBar that displays when the appBar is closed, but should not open the appBar
* when the appBar is closed.
*/
public class MyAppBarBehavior extends AppBarLayout.Behavior {
// Touch above this y-axis value can open the appBar.
private int mCanOpenBottom;
// Determines if the appBar can be dragged open or not via direct touch on the appBar.
private boolean mCanDrag = true;
@SuppressWarnings("unused")
public MyAppBarBehavior() {
init();
}
@SuppressWarnings("unused")
public MyAppBarBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
setDragCallback(new AppBarLayout.Behavior.DragCallback() {
@Override
public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
return mCanDrag;
}
});
}
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent,
AppBarLayout child,
MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// If appBar is closed. Only allow scrolling in defined area.
if (child.getTop() <= -child.getTotalScrollRange()) {
mCanDrag = event.getY() < mCanOpenBottom;
}
}
return super.onInterceptTouchEvent(parent, child, event);
}
public void setCanOpenBottom(int bottom) {
mCanOpenBottom = bottom;
}
}
_