app直播源码,列表越界后自动弹回原样的效果
app直播源码,列表越界后自动弹回原样的效果实现的相关代码
一、越界回弹效果的例子
二、效果拆分
下拉、上拉越界回弹:已到达列表顶部,执行下拉操作,此时会进行越界回弹
惯性滑动 越界回弹:快速滑动列表,列表已到达底部,但由于惯性会继续前进一部分,此时会根据速度决定越界的距离以及回弹的速度
三、下拉、上拉越界回弹实现方式及代码
在实际项目中可能在多个页面都需要回弹的效果,所以需要尽可能的减少与需要回弹view的耦合代码。所以可以自定义一个ViewGroup来包裹需要回弹的view。
1 | public class PullOverLayout extends RelativeLayout {<br> //允许的越界回弹的高度<br> protected float mOverScrollHeight;<br> //被包裹的子View<br> private View mChildView;<br> //~~~~~<br> public PullOverLayout(Context context, AttributeSet attrs, int defStyleAttr) {<br> super(context, attrs, defStyleAttr);<br> mOverScrollHeight = DensityUtil.dp2px(context, 240);<br> }<br> @Override<br> protected void onAttachedToWindow() {<br> super.onAttachedToWindow();<br> //获得子控件,也就是被包裹的recycleview等<br> mChildView = getChildAt(0);<br> }<br>} |
重写事件拦截方法onInterceptTouchEvent()
通过竖直和水平方向Move的距离来判断是否拦截该事件
dy大于0为手指向下滑动,dy小于0为手指向上滑动,通过setStatePTD、setStatePBU方法记录Down或者Up
当被包裹的子view无法滑动时即发生越界时拦截并响应该Move事件
1 | @Override<br> public boolean onInterceptTouchEvent(MotionEvent ev) {<br> switch (ev.getAction()) {<br> case MotionEvent.ACTION_DOWN:<br> mTouchX = ev.getX();<br> mTouchY = ev.getY();<br> break ;<br> case MotionEvent.ACTION_MOVE:<br> float dx = ev.getX() - mTouchX;<br> float dy = ev.getY() - mTouchY;<br> if (Math. abs (dx) <= Math. abs (dy)) {<br> if (dy > 0 && !ScrollingUtil.canChildScrollUp(mChildView)) {<br> //1、下拉操作<br> LogUtil.d(TAG, "setStatePTD");<br> cp.setStatePTD();<br> return true;<br> } else if (dy < 0 && !ScrollingUtil.canChildScrollDown(cp.getContent())) {<br> //2、上拉操作<br> LogUtil.d(TAG, "setStatePBU");<br> cp.setStatePBU();<br> return true;<br> }<br> }<br> break;<br> }<br> return super.onInterceptTouchEvent(ev);<br>} |
重写onTouchEvent()方法响应拦截的事件
onTouchEvent用来响应事件,当move和up时,做相应的“越界”操作和“回弹”操作
下拉和上拉操作分别执行对应的越界和回弹操作即可
1 | @Override<br> public boolean onTouchEvent(MotionEvent e) {<br> switch (e.getAction()) {<br> //当发生越界时由viewGroup响应Move事件<br> case MotionEvent.ACTION_MOVE:<br> float dy = e.getY() - mTouchY;<br> if (cp.isStatePTD()) {<br> //1、如果是下拉操作的move事件,则执行顶部越界操作<br> dy = Math.min(cp.getOsHeight() * 2, dy);<br> dy = Math.max(0, dy);<br> cp.getAnimProcessor().scrollHeadByMove(dy);<br> } else if (cp.isStatePBU()) {<br> //2、如果是上拉操作的move事件,则执行底部越界的操作<br> dy = Math.min(cp.getOsHeight() * 2, Math.abs(dy));<br> dy = Math.max(0, dy);<br> cp.getAnimProcessor().scrollBottomByMove(dy);<br> }<br> return true;<br> case MotionEvent.ACTION_CANCEL:<br> case MotionEvent.ACTION_UP:<br> if (cp.isStatePTD()) {<br> //3、如果是下拉操作,当收到Up事件时,进行释放操作下拉操作,即执行下拉回弹操作<br> cp.getAnimProcessor().dealPullDownRelease();<br> } else if (cp.isStatePBU()) {<br> //4、如果是上拉操作,当收到up事件时,进行释放上拉操作,执行上拉回弹操作<br> cp.getAnimProcessor().dealPullUpRelease();<br> }<br> return true;<br> }<br> return super.onTouchEvent(e);<br>} |
第三步onTouchEvent中的下拉越界方法scrollHeadByMove(float moveY)和下拉释放的回弹方法dealPullDownRelease()
1 | public void scrollHeadByMove(float moveY) {<br> float offsetY = decelerateInterpolator.getInterpolation(moveY / cp.getOsHeight() / 2) * moveY / 2;<br> LogUtil.d(TAG, "scrollHeadByMove " , "offsetY:" , String.valueOf(offsetY));<br> cp.getHeader().setVisibility(GONE);<br> cp.getHeader().getLayoutParams().height = (int) Math. abs (offsetY);<br> cp.getHeader().requestLayout();<br> cp.getContent().setTranslationY(offsetY);<br>}<br> public void dealPullDownRelease() {<br> int start = getVisibleHeadHeight();<br> int end = 0;<br> LogUtil.d(TAG, "dealPullDownRelease " , "start:" , String.valueOf(start), "end:" , String.valueOf( end ));<br> ValueAnimator va = ValueAnimator.ofInt(start, end );<br> va.setInterpolator( new DecelerateInterpolator());<br> va.addUpdateListener(animHeadUpListener);<br> va.setDuration((int) (Math. abs (start - end ) * animFraction));<br> va.start();<br>} |
四、惯性滑动 越界回弹实现方式及代码
给被包裹的子view设置监听
给定一个最低速度的阈值,当滑动速度超过这个值时根据惯性执行越界回弹操作
下方代码只实现了recycleview的越界回弹,listview类似。即被包裹的子view必须是recycleview
1 | public void initChildViewFlingListener() {<br> final View mChildView = cp.getContent();<br> //1、给被包裹的子view设置监听事件,监听fling操作,并记录速度<br> final GestureDetector gestureDetector = new GestureDetector(cp.getContext(), new GestureDetector.SimpleOnGestureListener() {<br> @Override<br> public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {<br> LogUtil.d(TAG, "onScroll ", "distanceY:", String.valueOf(distanceY));<br> return super.onScroll(e1, e2, distanceX, distanceY);<br> }<br> @Override<br> public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {<br> LogUtil.d(TAG, "onFling ", "velocityY:", String.valueOf(velocityY));<br> mVelocityY = velocityY;<br> return false;<br> }<br> });<br> mChildView.setOnTouchListener(new View.OnTouchListener() {<br> @Override<br> public boolean onTouch(View v, MotionEvent event) {<br> //手势监听的两个任务:1.监听fling动作,获取速度 2.监听滚动状态变化<br> return gestureDetector.onTouchEvent(event);<br> }<br> });<br> if (mChildView instanceof RecyclerView) {<br> ((RecyclerView) mChildView).addOnScrollListener(new RecyclerView.OnScrollListener() {<br> @Override<br> public void onScrollStateChanged(RecyclerView recyclerView, int newState) {<br> if (newState == RecyclerView.SCROLL_STATE_IDLE) {<br> //2、到达越界速度阈值后,执行fling的顶部越界操作<br> if (mVelocityY >= OVER_SCROLL_MIN_VX && ScrollingUtil.isRecyclerViewToTop((RecyclerView) mChildView)) {<br> cp.getAnimProcessor().animOverScrollTop(mVelocityY, cur_delay_times);<br> mVelocityY = 0;<br> cur_delay_times = ALL_DELAY_TIMES;<br> }<br> if (mVelocityY <= -OVER_SCROLL_MIN_VX && ScrollingUtil.isRecyclerViewToBottom((RecyclerView) mChildView)) {<br> cp.getAnimProcessor().animOverScrollBottom(mVelocityY, cur_delay_times);<br> mVelocityY = 0;<br> cur_delay_times = ALL_DELAY_TIMES;<br> }<br> }<br> super.onScrollStateChanged(recyclerView, newState);<br> }<br> });<br> }<br>} |
执行越界动画
底部越界和顶部越界代码类似,此处只讲解顶部越界即可
1 | <br> /**<br> * 执行顶部越界<br> * 越界高度height ∝ vy/computeTimes,此处采用的模型是height=A*(vy + B)/computeTimes<br> */ <br> public void animOverScrollTop(float vy, int computeTimes) {<br> LogUtil.d(TAG, "animOverScrollTop " , "vy:" , String.valueOf(vy), "computeTimes:" , String.valueOf(computeTimes));<br> if (cp.isOsTopLocked()) return ;<br> cp.lockOsTop();<br> cp.setStatePTD();<br> //1、计算越界高度,最大不超过设定的越界高度<br> int oh = (int) Math.abs(vy / computeTimes / 2);<br> final int overHeight = Math.min(oh, cp.getOsHeight());<br> //2、计算越界时间<br> final int time = overHeight <= 50 ? 115 : (int) (0.3 * overHeight + 100);<br> //3、执行越界操作<br> animLayoutByTime(0, overHeight, time, overScrollTopUpListener, new AnimatorListenerAdapter() {<br> @Override<br> public void onAnimationEnd(Animator animation) {<br> //4、执行回弹操作<br> animLayoutByTime(overHeight, 0, 2 * time, overScrollTopUpListener, new AnimatorListenerAdapter() {<br> @Override<br> public void onAnimationEnd(Animator animation) {<br> cp.releaseOsTopLock();<br> }<br> });<br> }<br> });<br>}<br>//最终执行的就是个属性动画<br>public void animLayoutByTime(int start, int end, long time, AnimatorUpdateListener listener, AnimatorListener animatorListener) {<br> ValueAnimator va = ValueAnimator.ofInt(start, end);<br> va.setInterpolator(new DecelerateInterpolator());<br> va.addUpdateListener(listener);<br> va.addListener(animatorListener);<br> va.setDuration(time);<br> va.start();<br>} |
以上就是 app直播源码,列表越界后自动弹回原样的效果实现的相关代码,更多内容欢迎关注之后的文章
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现