北京门户网站有哪些,网站设计的总结,万源网站建设,wordpress所有文章404需求
在移动应用开发中#xff0c;有时我们希望实现一种特殊的布局效果#xff0c;即“底部二楼”效果。这个效果类似于在列表底部拖动时出现额外的内容区域#xff0c;用户可以继续向上拖动查看更多内容。这种效果可以用于展示广告、推荐内容或其他信息。
效果
实现后的…需求
在移动应用开发中有时我们希望实现一种特殊的布局效果即“底部二楼”效果。这个效果类似于在列表底部拖动时出现额外的内容区域用户可以继续向上拖动查看更多内容。这种效果可以用于展示广告、推荐内容或其他信息。
效果
实现后的效果如下 当用户滑动到列表底部时可以继续向上拖动显示出隐藏的底部内容区域。底部内容区域可以包含任意视图如RecyclerView等。滑动到一定阈值后可以自动回弹到初始位置或完全展示底部内容。
实现思路
为了实现这一效果我们可以自定义一个ScrollerLayout并使用Scroller类来处理滑动和回弹动画。主要思路如下
创建自定义的ScrollerLayout继承自LinearLayout。在ScrollerLayout中遍历所有子视图找到其中的RecyclerView并为其添加滚动监听器。在RecyclerView滚动到顶部时允许整个布局继续向上滑动展示底部内容区域。使用Scroller类实现平滑滚动和回弹效果。
实现代码
ScrollerLayout.kt
package com.yxlh.androidxy.demo.ui.scrollerimport android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.widget.LinearLayout
import android.widget.Scroller
import androidx.recyclerview.widget.RecyclerView
import com.yxlh.androidxy.R//github.com/yixiaolunhui/AndroidXY
class ScrollerLayout JvmOverloads constructor(context: Context,attrs: AttributeSet? null,defStyleAttr: Int 0,
) : LinearLayout(context, attrs, defStyleAttr) {private val mScroller Scroller(context)private var lastY 0private var downY 0private var contentHeight 0private var isRecyclerViewAtTop falseprivate val touchSlop ViewConfiguration.get(context).scaledTouchSlopinit {orientation VERTICALpost {setupRecyclerViews()}}private fun setupRecyclerViews() {for (i in 0 until childCount) {val child getChildAt(i)if (child is RecyclerView) {child.addOnScrollListener(object : RecyclerView.OnScrollListener() {override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {isRecyclerViewAtTop !recyclerView.canScrollVertically(-1)}})}}}override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {super.onLayout(changed, l, t, r, b)val bottomBar getChildAt(0)contentHeight 0for (i in 0 until childCount) {val child getChildAt(i)if (child is RecyclerView) {contentHeight child.measuredHeight}}bottomBar.layout(0, measuredHeight - bottomBar.measuredHeight, measuredWidth, measuredHeight)for (i in 1 until childCount) {val child getChildAt(i)if (child is RecyclerView) {child.layout(0, measuredHeight, measuredWidth, measuredHeight contentHeight)}}}override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {val isTouchChildren isTouchInsideChild(ev)Log.d(121212, onInterceptTouchEvent isTouchChildren$isTouchChildren)when (ev.action) {MotionEvent.ACTION_DOWN - {downY ev.y.toInt()lastY downY}MotionEvent.ACTION_MOVE - {val currentY ev.y.toInt()val dy currentY - downYif (isRecyclerViewAtTop dy touchSlop) {lastY currentYreturn true}}}return super.onInterceptTouchEvent(ev)}override fun onTouchEvent(event: MotionEvent): Boolean {when (event.action) {MotionEvent.ACTION_DOWN - {if (!isTouchInsideChild(event)) return falseif (!mScroller.isFinished) {mScroller.abortAnimation()}lastY event.y.toInt()return true}MotionEvent.ACTION_MOVE - {if (!isTouchInsideChild(event)) return falseval currentY event.y.toInt()val dy lastY - currentYval scrollY scrollY dyif (scrollY 0) {scrollTo(0, 0)} else if (scrollY contentHeight) {scrollTo(0, contentHeight)} else {scrollBy(0, dy)}lastY currentYreturn true}MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL - {val threshold contentHeight / 2if (scrollY threshold) {showNavigation()} else {closeNavigation()}return true}}return false}private fun isTouchInsideChild(event: MotionEvent): Boolean {val x event.rawX.toInt()val y event.rawY.toInt()for (i in 0 until childCount) {val child getChildAt(i)if (isViewUnder(child, x, y)) {return true}}return false}private fun isViewUnder(view: View?, x: Int, y: Int): Boolean {if (view null) return falseval location IntArray(2)view.getLocationOnScreen(location)val viewX location[0]val viewY location[1]return x viewX x viewX view.width y viewY y viewY view.height}fun showNavigation() {val dy contentHeight - scrollYmScroller.startScroll(scrollX, scrollY, 0, dy, 500)invalidate()}private fun closeNavigation() {val dy -scrollYmScroller.startScroll(scrollX, scrollY, 0, dy, 500)invalidate()}override fun computeScroll() {if (mScroller.computeScrollOffset()) {scrollTo(mScroller.currX, mScroller.currY)postInvalidateOnAnimation()}}
}ScrollerActivity.kt
package com.yxlh.androidxy.demo.ui.scrollerimport android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.yxlh.androidxy.R
import com.yxlh.androidxy.databinding.ActivityScrollerBinding
import kotlin.random.Randomclass ScrollerActivity : AppCompatActivity() {private var binding: ActivityScrollerBinding? nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding ActivityScrollerBinding.inflate(layoutInflater)setContentView(binding?.root)//内容布局binding?.content?.layoutManager LinearLayoutManager(this)binding?.content?.adapter ColorAdapter(false)//底部布局binding?.bottomContent?.layoutManager LinearLayoutManager(this)binding?.bottomContent?.adapter ColorAdapter(true)binding?.content?.addOnScrollListener(object : RecyclerView.OnScrollListener() {override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {if (!recyclerView.canScrollVertically(1) newState RecyclerView.SCROLL_STATE_IDLE) {binding?.scrollerLayout?.showNavigation()}}})}
}class ColorAdapter(private var isColor: Boolean) : RecyclerView.AdapterColorAdapter.ColorViewHolder() {private val colors List(100) { getRandomColor() }override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ColorViewHolder {val view LayoutInflater.from(parent.context).inflate(R.layout.item_color, parent, false)return ColorViewHolder(view, isColor)}override fun onBindViewHolder(holder: ColorViewHolder, position: Int) {holder.bind(colors[position], position)}override fun getItemCount(): Int colors.sizeprivate fun getRandomColor(): Int {val random Random.Defaultreturn Color.rgb(random.nextInt(256), random.nextInt(256), random.nextInt(256))}class ColorViewHolder(itemView: View, private var isColor: Boolean) : RecyclerView.ViewHolder(itemView) {fun bind(color: Int, position: Int) {if (isColor) {itemView.setBackgroundColor(color)}itemView.findViewByIdTextView(R.id.color_tv).text $position}}
}结束
通过上述代码我们成功实现了底部二楼效果。在用户滑动到RecyclerView底部时可以继续向上拖动以显示底部的内容区域。这种效果可以增强用户体验增加更多的内容展示方式。通过自定义布局和使用Scroller类我们可以轻松实现这种复杂的滑动效果。 详情github.com/yixiaolunhui/AndroidXY