Android开发项目实战:实现折叠式布局,程序员开发指南

public abstract class AppBarStateChangeListener implements AppBarLayout.OnOffsetChangedListener {

public enum State {

EXPANDED,

COLLAPSED,

IDLE

}

private State mCurrentState = State.IDLE;

@Override

public final void onOffsetChanged(AppBarLayout appBarLayout, int i) {

if (i == 0) {

if (mCurrentState != State.EXPANDED) {

onStateChanged(appBarLayout, State.EXPANDED);

}

mCurrentState = State.EXPANDED;

} else if (Math.abs(i) >= appBarLayout.getTotalScrollRange()) {

if (mCurrentState != State.COLLAPSED) {

onStateChanged(appBarLayout, State.COLLAPSED);

}

mCurrentState = State.COLLAPSED;

} else {

if (mCurrentState != State.IDLE) {

onStateChanged(appBarLayout, State.IDLE);

}

mCurrentState = State.IDLE;

}

}

public abstract void onStateChanged(AppBarLayout appBarLayout, State state);

}

然后我们这样使用它:

mAppBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() {

@Override

public void onStateChanged(AppBarLayout appBarLayout, State state) {

Log.d(“STATE”, state.name());

if( state == State.EXPANDED ) {

//展开状态

}else if(state == State.COLLAPSED){

//折叠状态

}else {

//中间状态

}

}

});

这样就可以在不同的状态下根据自己的业务需求去实现相关的逻辑了

StickyLayout自定折叠式布局的实现

好了,上面就是关于通过CoordinateLayout实现的折叠式布局所有的知识点,如果说前面只是开胃菜,现在我们就开始上主菜了,我们能不能自己实现这样一个折叠式的布局,利用上一篇我们所讲的头部固定的ExpandedListView,把它作为具有滑动功能的主View,在它的顶部添加具有背景图片Header,随着ExpandedListView的滑动header实现扩展和收缩的效果,效果如下:

功能分析

其实这个效果图在文章的一开始就展示过了,整个布局分为上下两部分:上分部分为可折叠的Header,下半部分就是我们头部固定的ExpandedListView,他们公共父view就是今天我们要实现的折叠式布局StickyLayout,ExpandedListView是自身所具备滑动功能的,而我们在整个屏幕上,往上滑动的时候如果header处于展开状态则Header慢慢的要折叠起来,往下滑动的时候如果ExpandedListView顶部数据都显示出来的情况下再往下拉的时候Header就慢慢的展开,其他的状态就是我们的ExpandedListView在上下滑动,也就是说我们的Header在折叠和展开的状态下的这些事件被StickyLayout拦截了,其他的事件就交给ExpandedListView进行处理从而实现了他的上下滑动,这就属于典型的滑动冲突问题,简言之就是我们在上下滑动的过程中的有些事件需要被StickyLayout拦截消掉来实现Header的折叠和展开效果,其他的事件就交给ExpandedListView来实现它的滑动效果

现在我们要思考的是哪些情况下被拦截:

  • 左右滑动的不需要处理,只处理上下滑动的事件

  • 在展开的状态下,上滑事件需要拦截

  • ExpandedListView的第0个元素处于可见状态,此时的下滑事件需要拦截

在事件拦截方法中处理滑动冲突

public class StickyLayout extends LinearLayout {

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

boolean intercept = false;

int x = (int) ev.getX();

int y = (int) ev.getY();

switch (ev.getAction()){

case MotionEvent.ACTION_DOWN:

mLastInterceptX = x;

mLastInterceptY = y;

intercept = false;

break;

case MotionEvent.ACTION_MOVE:

int dx = x - mLastInterceptX;

int dy = y - mLastInterceptY;

if(y <= mCurrHeaderHeight){

intercept = false;

}else if(Math.abs(dx) > Math.abs(dy)){

intercept = false;

}else if(mState == mStateExpand && dy <= - mScaledTouchSlop){

//上滑

intercept = true;

}else if(mGiveUpTouchEventListener.giveUpTouchEvent() && dy > mScaledTouchSlop){

//下滑

intercept = true;

}else{

intercept = false;

}

break;

case MotionEvent.ACTION_UP:

mLastInterceptX = 0;

mLastInterceptY = 0;

intercept = false;

break;

}

return intercept;

}

}

上面就是关于事件拦截的核心代码,首先我们看17行:y <= mCurrHeaderHeight 如果触摸事件是在Header之上也就不拦截了,再看19行Math.abs(dx) > Math.abs(dy),如果是横向滑动也不是我们所需要的事件也不拦截,否则上就是上下滑动的事件了,在这个状态下状态Header处于展开状态且是上滑那就需要拦截处理,也就是21行:mState == mStateExpand && dy <= - mScaledTouchSlop所处理的逻辑,在看24行:mGiveUpTouchEventListener.giveUpTouchEvent() && dy > mScaledTouchSlop,giveUpTouchEvent方法表示如果ExpandedListView的第一个可见元素是0且dy > mScaledTouchSlop(表示是上滑)此时的事件也是需要拦截的

View滑动距离常量TouchSlop

在21行细心的同学可能会看到这么一句dy <= - mScaledTouchSlop,dy指的是滑动的距离,mScaledTouchSlop到底是什么?其实他是Android系统给我们提供的View滑动最小距离常量TouchSlop,也就是说两个Move事件之间的滑动距离如果小于这个常量就系统不认为他是滑动,因为滑动距离太短,反之就认为它是滑动,这个常量值和设备有关,不同的设置上这个值可能是不相同的,我们可以通过如下方式即可获取这个常量:

int mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();

折叠展开的事件消费

上面的17到29行就是处理事件拦截的核心处理逻辑,事件拦截完毕,事件就交给TouchEvent方法进行消费了,下面看看Header到底具体是怎么折叠的?其实很简单就是不用重置Header的height就OK了,我们看看代码:

public class StickyLayout extends LinearLayout {

@Override

public boolean onTouchEvent(MotionEvent event) {

int x = (int) event.getX();

int y = (int) event.getY();

switch (event.getAction()){

case MotionEvent.ACTION_DOWN:

break;

case MotionEvent.ACTION_MOVE:

int dx = x - mLastX;

int dy = y - mLastY;

mCurrHeaderHeight += dy;

setHeaderHeight(mCurrHeaderHeight);

break;

case MotionEvent.ACTION_UP:

int dest = 0;

if(mCurrHeaderHeight <= mOriginHeaderHeight * 0.5){

dest = 0;

mState = mStateCollapsed;

}else{

dest = mOriginHeaderHeight;

mState = mStateExpand;

}

smoothSetHeaderHeight(mCurrHeaderHeight,dest,500);

break;

}

mLastX = x;

mLastY = y;

return super.onTouchEvent(event);

}

}

其中12行到13行就是手指拖动状态下的核心逻辑 ,12行计算两次Move事件所移动的距离,13行根据手指滑动的距离来计算Header当前的高度,计算完毕就可以调用setHeaderHeight设置Header的高了

设置Header的高来实现折叠效果

private void setHeaderHeight(int height) {

if(height <= 0){

height = 0;

}else if(height >= mOriginHeaderHeight){

height = mOriginHeaderHeight;

}

if(height == 0 ){

mState = mStateCollapsed;

}else{

mState = mStateExpand;

}

headerView.getLayoutParams().height = height;

headerView.requestLayout();

}

其中第2行到第6行对Header高度的越界处理,第7行到11行是设置Header的状态,第12行到13行给Header的高赋值并刷新Header来变它的位置与大小

手指抬起的自动回弹折叠展开效果

如果当前Header的高小于原始高度的一半手指抬起的时候Header进行收缩,反之就进行展开操作,核心代码在上面的onTouchEvent(MotionEvent event)方法的的17行到25行:

int dest = 0;

if(mCurrHeaderHeight <= mOriginHeaderHeight * 0.5){

dest = 0;

mState = mStateCollapsed;

}else{

dest = mOriginHeaderHeight;

mState = mStateExpand;

}

smoothSetHeaderHeight(mCurrHeaderHeight,dest,500);

最后在调用smoothSetHeaderHeight实现弹性展开,折叠

private void smoothSetHeaderHeight(int from,int to,int duration) {

ValueAnimator valueAnimator = ValueAnimator.ofInt(from, to).setDuration(duration);

valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator animation) {

setHeaderHeight((Integer) animation.getAnimatedValue());

}

});

valueAnimator.start();

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后

文章不易,如果大家喜欢这篇文章,或者对你有帮助希望大家多多点赞转发关注哦。文章会持续更新的。绝对干货!!!

  • Android进阶学习全套手册
    关于实战,我想每一个做开发的都有话要说,对于小白而言,缺乏实战经验是通病,那么除了在实际工作过程当中,我们如何去更了解实战方面的内容呢?实际上,我们很有必要去看一些实战相关的电子书。目前,我手头上整理到的电子书还算比较全面,HTTP、自定义view、c++、MVP、Android源码设计模式、Android开发艺术探索、Java并发编程的艺术、Android基于Glide的二次封装、Android内存优化——常见内存泄露及优化方案、.Java编程思想 (第4版)等高级技术都囊括其中。

  • Android高级架构师进阶知识体系图
    关于视频这块,我也是自己搜集了一些,都按照Android学习路线做了一个分类。按照Android学习路线一共有八个模块,其中视频都有对应,就是为了帮助大家系统的学习。接下来看一下导图和对应系统视频吧!!!

  • Android对标阿里P7学习视频

  • BATJ大厂Android高频面试题
    这个题库内容是比较多的,除了一些流行的热门技术面试题,如Kotlin,数据库,Java虚拟机面试题,数组,Framework ,混合跨平台开发,等

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

in,数据库,Java虚拟机面试题,数组,Framework ,混合跨平台开发,等
[外链图片转存中…(img-EHHkB79x-1712120450590)]

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!

AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算

  • 17
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ToggleExpandLayout是一个可折叠展开子view的开关布局控件。它可以将它的子view以阶梯式的展开。项目地址:https://github.com/fenjuly/ToggleExpandLayout 效果图:如何使用<com.fenjuly.mylibrary.ToggleExpandLayout             android:id="@ id/toogleLayout"             android:layout_width="wrap_content"             android:layout_height="80dp"             >             <TextView                 android:layout_width="wrap_content"                 android:layout_height="wrap_content"                 android:text="view 1"/>             <TextView                 android:layout_width="wrap_content"                 android:layout_height="wrap_content"                 android:text="view 2"/>             <TextView                 android:layout_width="wrap_content"                 android:layout_height="wrap_content"                 android:text="view"/>                      </com.fenjuly.mylibrary.ToggleExpandLayout>注意,由于ToggleExpandLayout的本质是个FrameLayout,所以必须将其高度设置为大于所有子view展开状态的高度,不能设为wrap_content。为了解决这个问题,你可以将ToggleExpandLayout的外面在加个DropDownLayout:<com.fenjuly.mylibrary.DropDownLayout         android:layout_width="match_parent"         android:layout_height="match_parent"         >         <com.fenjuly.mylibrary.ToggleExpandLayout             android:id="@ id/toogleLayout"             android:layout_width="wrap_content"             android:layout_height="wrap_content"             >             <TextView                 android:layout_width="wrap_content"                 android:layout_height="wrap_content"                 android:text="view 1"/>              <TextView                 android:layout_width="wrap_content"                 android:layout_height="wrap_content"                 android:text="view 2"/>               <TextView                 android:layout_width="wrap_content"                 android:layout_height="wrap_content"                 android:text="view"/>                              </com.fenjuly.mylibrary.ToggleExpandLayout> </com.fenjuly.mylibrary.DropDownL
实现按钮控制布局的折叠,可以使用 Android 中的 `ConstraintLayout` 和 `ConstraintSet`。 首先,在 XML 布局文件中,将需要折叠的布局放在一个 `ConstraintLayout` 中,并添加一个按钮,用于控制折叠。例如: ```xml <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/collapsedLayout" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/someView"> <!-- 折叠的布局 --> <TextView android:id="@+id/collapsedTextView" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/colorAccent" android:text="Collapsed Text" android:textColor="@android:color/white" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> <!-- 控制折叠的按钮 --> <Button android:id="@+id/expandButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Expand" app:layout_constraintTop_toBottomOf="@id/collapsedTextView" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> ``` 接下来,在 Java/Kotlin 代码中,将需要折叠的布局和按钮分别用 `findViewById` 获取,并创建一个 `ConstraintSet` 对象,用于设置折叠和展开后的布局约束。例如: ```kotlin val collapsedLayout = findViewById<ConstraintLayout>(R.id.collapsedLayout) val collapsedTextView = findViewById<TextView>(R.id.collapsedTextView) val expandButton = findViewById<Button>(R.id.expandButton) // 创建 ConstraintSet 对象 val constraintSet = ConstraintSet() // 设置折叠后的布局约束 constraintSet.clone(collapsedLayout) constraintSet.constrainHeight(R.id.collapsedTextView, 0) // 设置展开后的布局约束 val expandedHeight = resources.getDimensionPixelSize(R.dimen.expanded_height) constraintSet.constrainHeight(R.id.collapsedTextView, expandedHeight) // 点击按钮控制折叠和展开 expandButton.setOnClickListener { if (collapsedTextView.height == 0) { // 折叠状态,展开 constraintSet.applyTo(collapsedLayout) expandButton.text = "Collapse" } else { // 展开状态,折叠 constraintSet.applyTo(collapsedLayout) expandButton.text = "Expand" } } ``` 这样,点击按钮时即可控制布局的折叠和展开

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值