1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.intentresolver.widget 18 19 import android.content.Context 20 import android.util.AttributeSet 21 import android.view.View 22 import android.widget.LinearLayout 23 import androidx.core.view.ScrollingView 24 import androidx.core.view.marginBottom 25 import androidx.core.view.marginLeft 26 import androidx.core.view.marginRight 27 import androidx.core.view.marginTop 28 import androidx.core.widget.NestedScrollView 29 30 /** 31 * A narrowly tailored [NestedScrollView] to be used inside [ResolverDrawerLayout] and help to 32 * orchestrate content preview scrolling. It expects one [LinearLayout] child with 33 * [LinearLayout.VERTICAL] orientation. If the child has more than one child, the first its child 34 * will be made scrollable (it is expected to be a content preview view). 35 */ 36 class ChooserNestedScrollView : NestedScrollView { 37 constructor(context: Context) : super(context) 38 constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 39 constructor( 40 context: Context, 41 attrs: AttributeSet?, 42 defStyleAttr: Int 43 ) : super(context, attrs, defStyleAttr) 44 onMeasurenull45 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 46 val content = 47 getChildAt(0) as? LinearLayout ?: error("Exactly one child, LinerLayout, is expected") 48 require(content.orientation == LinearLayout.VERTICAL) { "VERTICAL orientation is expected" } 49 require(MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) { 50 "Expected to have an exact width" 51 } 52 53 val lp = content.layoutParams ?: error("LayoutParams is missing") 54 val contentWidthSpec = 55 getChildMeasureSpec( 56 widthMeasureSpec, 57 paddingLeft + content.marginLeft + content.marginRight + paddingRight, 58 lp.width 59 ) 60 val contentHeightSpec = 61 getChildMeasureSpec( 62 heightMeasureSpec, 63 paddingTop + content.marginTop + content.marginBottom + paddingBottom, 64 lp.height 65 ) 66 content.measure(contentWidthSpec, contentHeightSpec) 67 68 if (content.childCount > 1) { 69 // We expect that the first child should be scrollable up 70 val child = content.getChildAt(0) 71 val height = 72 MeasureSpec.getSize(heightMeasureSpec) + 73 child.measuredHeight + 74 child.marginTop + 75 child.marginBottom 76 77 content.measure( 78 contentWidthSpec, 79 MeasureSpec.makeMeasureSpec(height, MeasureSpec.getMode(heightMeasureSpec)) 80 ) 81 } 82 setMeasuredDimension( 83 MeasureSpec.getSize(widthMeasureSpec), 84 minOf( 85 MeasureSpec.getSize(heightMeasureSpec), 86 paddingTop + 87 content.marginTop + 88 content.measuredHeight + 89 content.marginBottom + 90 paddingBottom 91 ) 92 ) 93 } 94 onNestedPreScrollnull95 override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) { 96 // let the parent scroll 97 super.onNestedPreScroll(target, dx, dy, consumed, type) 98 // scroll ourselves, if recycler has not scrolled 99 val delta = dy - consumed[1] 100 if (delta > 0 && target is ScrollingView && !target.canScrollVertically(-1)) { 101 val preScrollY = scrollY 102 scrollBy(0, delta) 103 consumed[1] += scrollY - preScrollY 104 } 105 } 106 } 107