├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── zwl │ │ └── mybossdemo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── zwl │ │ │ └── mybossdemo │ │ │ ├── MainActivity.java │ │ │ ├── company │ │ │ └── BossCompanyDetailActivity.java │ │ │ ├── dialog │ │ │ └── CommonBottomSheetDialog.java │ │ │ ├── filter │ │ │ ├── FilterActivity.java │ │ │ ├── FilterBean.java │ │ │ ├── FilterDataUtils.java │ │ │ ├── FilterGrop.java │ │ │ ├── FilterLayout.java │ │ │ └── flow │ │ │ │ ├── FlowLayout.java │ │ │ │ ├── TagFlowAdapter.java │ │ │ │ ├── TagFlowAdapter2.java │ │ │ │ ├── TagFlowContainer.java │ │ │ │ └── TagFlowLayout.java │ │ │ ├── mine │ │ │ ├── BossMineActivity.java │ │ │ └── behavior │ │ │ │ └── AppBarLayoutOverScrollViewBehavior.java │ │ │ └── note │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xxhdpi │ │ ├── boss_avatar_7.png │ │ ├── boss_card_bg.jpg │ │ ├── boss_company_detail.jpg │ │ ├── boss_company_detail_bottom.jpg │ │ ├── boss_company_detail_bottom_top.jpg │ │ ├── boss_filter_guanbi.png │ │ ├── boss_icon_down.png │ │ ├── boss_icon_up.png │ │ └── boss_user_bottom.jpg │ │ ├── drawable │ │ ├── filter_btn2_bg.xml │ │ ├── filter_btn_bg.xml │ │ ├── ic_launcher_background.xml │ │ ├── tag_bg_tag.xml │ │ └── tag_textcolor.xml │ │ ├── layout │ │ ├── activity_boss_mine.xml │ │ ├── activity_company_detail.xml │ │ ├── activity_filter.xml │ │ ├── activity_main.xml │ │ ├── dialog_view.xml │ │ ├── item_filter_tag.xml │ │ ├── item_filter_view.xml │ │ ├── layout_content.xml │ │ └── layout_top_view.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── zwl │ └── mybossdemo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── video ├── boss.gif ├── boss.mp4 ├── flow.gif └── mine.gif /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MyBossDemo 2 | 仿Boss我的界面效果 3 | 4 | # 效果图 5 | ## 我的界面 6 | ![image](https://github.com/dalong982242260/MyBossDemo/blob/master/video/mine.gif?imageView/2/w/200/q/90) 7 | ## 筛选界面 8 | ![image](https://github.com/dalong982242260/MyBossDemo/blob/master/video/flow.gif?imageView/2/w/300/q/90) 9 | 10 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.3" 6 | 7 | defaultConfig { 8 | applicationId "com.zwl.mybossdemo" 9 | minSdkVersion 18 10 | targetSdkVersion 29 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | implementation 'com.android.support:design:29.0.3' 29 | implementation 'androidx.appcompat:appcompat:1.0.2' 30 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 31 | testImplementation 'junit:junit:4.12' 32 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 33 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 34 | 35 | implementation 'de.hdodenhof:circleimageview:3.1.0' 36 | implementation 'com.gyf.immersionbar:immersionbar:3.0.0' 37 | } 38 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/zwl/mybossdemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.zwl.mybossdemo; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.zwl.mybossdemo", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/zwl/mybossdemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.zwl.mybossdemo; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | 8 | import androidx.appcompat.app.AppCompatActivity; 9 | 10 | import com.zwl.mybossdemo.company.BossCompanyDetailActivity; 11 | import com.zwl.mybossdemo.dialog.CommonBottomSheetDialog; 12 | import com.zwl.mybossdemo.filter.FilterActivity; 13 | import com.zwl.mybossdemo.mine.BossMineActivity; 14 | 15 | public class MainActivity extends AppCompatActivity { 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_main); 21 | 22 | 23 | } 24 | 25 | /** 26 | * 我的界面 27 | * 28 | * @param view 29 | */ 30 | public void minePage(View view) { 31 | startActivity(new Intent(this, BossMineActivity.class)); 32 | } 33 | 34 | 35 | /** 36 | * 公司详情 37 | * 38 | * @param view 39 | */ 40 | public void companyDetail(View view) { 41 | startActivity(new Intent(this, BossCompanyDetailActivity.class)); 42 | } 43 | 44 | 45 | /** 46 | * 弹出框(分享,待遇等) 47 | * 48 | * @param view 49 | */ 50 | public void commonDialog(View view) { 51 | View content = LayoutInflater.from(this).inflate(R.layout.dialog_view, null); 52 | 53 | //默认的效果 54 | // BottomSheetDialog bottomSheetDialog=new BottomSheetDialog(this); 55 | // bottomSheetDialog.setContentView(content); 56 | // bottomSheetDialog.show(); 57 | 58 | 59 | //自定义的BottomSheetDialog 主要是解决高度问题 60 | final float scale = getResources().getDisplayMetrics().density; 61 | int height = (int) (500 * scale + 0.5f); 62 | CommonBottomSheetDialog bottomSheetDialog = new CommonBottomSheetDialog(this, height, height); 63 | bottomSheetDialog.setContentView(content); 64 | bottomSheetDialog.show(); 65 | 66 | 67 | } 68 | 69 | /** 70 | * 筛选界面 71 | * 72 | * @param view 73 | */ 74 | public void filter(View view) { 75 | startActivity(new Intent(this, FilterActivity.class)); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/zwl/mybossdemo/company/BossCompanyDetailActivity.java: -------------------------------------------------------------------------------- 1 | package com.zwl.mybossdemo.company; 2 | 3 | import android.graphics.Color; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.util.Log; 7 | import android.view.View; 8 | import android.view.ViewConfiguration; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.RequiresApi; 12 | import androidx.appcompat.app.AppCompatActivity; 13 | import androidx.appcompat.widget.Toolbar; 14 | import androidx.core.widget.NestedScrollView; 15 | 16 | import com.google.android.material.bottomsheet.BottomSheetBehavior; 17 | import com.zwl.mybossdemo.R; 18 | 19 | /** 20 | * Boss公司详情 21 | */ 22 | public class BossCompanyDetailActivity extends AppCompatActivity { 23 | 24 | BottomSheetBehavior bottomSheetBehavior; 25 | Toolbar toolbar; 26 | NestedScrollView contentLayout; 27 | private int mTouchSlop; 28 | 29 | @RequiresApi(api = Build.VERSION_CODES.M) 30 | @Override 31 | protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_company_detail); 34 | 35 | contentLayout = findViewById(R.id.content_view); 36 | 37 | //设置标题 38 | toolbar = findViewById(R.id.toolbar); 39 | toolbar.setTitle("Boss"); 40 | toolbar.setTitleTextColor(Color.WHITE); 41 | 42 | 43 | //设置底部 44 | View bottomSheet = findViewById(R.id.boss_bottom_sheet); 45 | bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet); 46 | bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { 47 | /** 48 | * 状态的改变 49 | * @param bottomSheet 50 | * @param newState 51 | */ 52 | @Override 53 | public void onStateChanged(@NonNull View bottomSheet, int newState) { 54 | } 55 | 56 | /** 57 | * 拖拽回调 58 | * @param bottomSheet 59 | * @param slideOffset 60 | */ 61 | @Override 62 | public void onSlide(@NonNull View bottomSheet, float slideOffset) { 63 | } 64 | }); 65 | 66 | 67 | mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop(); 68 | contentLayout.setOnScrollChangeListener(new View.OnScrollChangeListener() { 69 | @Override 70 | public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { 71 | /** 72 | * 上滑 73 | */ 74 | if (scrollY - oldScrollY > mTouchSlop) { 75 | Log.e("onScrollChange", "上滑"); 76 | if (bottomSheetBehavior.getState() != BottomSheetBehavior.STATE_HIDDEN) { 77 | bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); 78 | } 79 | } 80 | 81 | /** 82 | * 下滑 83 | */ 84 | else if (scrollY - oldScrollY < -mTouchSlop) { 85 | Log.e("onScrollChange", "下滑"); 86 | if (bottomSheetBehavior.getState() != BottomSheetBehavior.STATE_COLLAPSED) { 87 | bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); 88 | } 89 | } 90 | } 91 | }); 92 | } 93 | 94 | public void changeBottom(View view) { 95 | /** 96 | * 根据当前状态切换 开关 97 | */ 98 | if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) { 99 | bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); 100 | } else { 101 | bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); 102 | } 103 | } 104 | 105 | 106 | public boolean isShow() { 107 | if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN) { 108 | return false; 109 | } 110 | return true; 111 | } 112 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zwl/mybossdemo/dialog/CommonBottomSheetDialog.java: -------------------------------------------------------------------------------- 1 | package com.zwl.mybossdemo.dialog; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.view.Gravity; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.view.Window; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.StyleRes; 12 | 13 | import com.google.android.material.bottomsheet.BottomSheetBehavior; 14 | import com.google.android.material.bottomsheet.BottomSheetDialog; 15 | import com.zwl.mybossdemo.R; 16 | 17 | /** 18 | * 1、增加了设置显示高度跟最大高度的方法 19 | * 2、修复了通过手势关闭后无法再显示的问题 20 | */ 21 | public class CommonBottomSheetDialog extends BottomSheetDialog { 22 | 23 | private int mPeekHeight; 24 | private int mMaxHeight; 25 | private boolean mCreated; 26 | private Window mWindow; 27 | private BottomSheetBehavior mBottomSheetBehavior; 28 | 29 | public CommonBottomSheetDialog(@NonNull Context context) { 30 | super(context); 31 | mWindow = getWindow(); 32 | } 33 | 34 | public CommonBottomSheetDialog(@NonNull Context context, int peekHeight, int maxHeight) { 35 | this(context); 36 | mPeekHeight = peekHeight; 37 | mMaxHeight = maxHeight; 38 | } 39 | 40 | public CommonBottomSheetDialog(@NonNull Context context, @StyleRes int theme) { 41 | super(context, theme); 42 | mWindow = getWindow(); 43 | } 44 | 45 | public CommonBottomSheetDialog(@NonNull Context context, boolean cancelable, OnCancelListener cancelListener) { 46 | super(context, cancelable, cancelListener); 47 | } 48 | 49 | @Override 50 | protected void onCreate(Bundle savedInstanceState) { 51 | super.onCreate(savedInstanceState); 52 | 53 | mCreated = true; 54 | 55 | setPeekHeight(); 56 | setMaxHeight(); 57 | setBottomSheetCallback(); 58 | } 59 | 60 | /** 61 | * 显示高度 62 | * @param peekHeight 63 | */ 64 | public void setPeekHeight(int peekHeight) { 65 | mPeekHeight = peekHeight; 66 | if (mCreated) { 67 | setPeekHeight(); 68 | } 69 | } 70 | 71 | /** 72 | * 设置最大高度 73 | * @param height 74 | */ 75 | public void setMaxHeight(int height) { 76 | mMaxHeight = height; 77 | if (mCreated) { 78 | setMaxHeight(); 79 | } 80 | } 81 | 82 | public void setBatterSwipeDismiss(boolean enabled) { 83 | if (enabled) { 84 | 85 | } 86 | } 87 | 88 | private void setPeekHeight() { 89 | if (mPeekHeight <= 0) { 90 | return; 91 | } 92 | 93 | if (getBottomSheetBehavior() != null) { 94 | mBottomSheetBehavior.setPeekHeight(mPeekHeight); 95 | } 96 | } 97 | 98 | private void setMaxHeight() { 99 | if (mMaxHeight <= 0) { 100 | return; 101 | } 102 | 103 | mWindow.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, mMaxHeight); 104 | mWindow.setGravity(Gravity.BOTTOM); 105 | } 106 | 107 | private BottomSheetBehavior getBottomSheetBehavior() { 108 | if (mBottomSheetBehavior != null) { 109 | return mBottomSheetBehavior; 110 | } 111 | 112 | View view = mWindow.findViewById(R.id.design_bottom_sheet); 113 | // setContentView() 没有调用 114 | if (view == null) { 115 | return null; 116 | } 117 | mBottomSheetBehavior = BottomSheetBehavior.from(view); 118 | return mBottomSheetBehavior; 119 | } 120 | 121 | private void setBottomSheetCallback() { 122 | if (getBottomSheetBehavior() != null) { 123 | mBottomSheetBehavior.setBottomSheetCallback(mBottomSheetCallback); 124 | } 125 | } 126 | 127 | private final BottomSheetBehavior.BottomSheetCallback mBottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() { 128 | @Override 129 | public void onStateChanged(@NonNull View bottomSheet, 130 | @BottomSheetBehavior.State int newState) { 131 | if (newState == BottomSheetBehavior.STATE_HIDDEN) { 132 | dismiss(); 133 | BottomSheetBehavior.from(bottomSheet).setState( 134 | BottomSheetBehavior.STATE_COLLAPSED); 135 | } 136 | } 137 | 138 | @Override 139 | public void onSlide(@NonNull View bottomSheet, float slideOffset) { 140 | } 141 | }; 142 | } 143 | -------------------------------------------------------------------------------- /app/src/main/java/com/zwl/mybossdemo/filter/FilterActivity.java: -------------------------------------------------------------------------------- 1 | package com.zwl.mybossdemo.filter; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import androidx.appcompat.app.AppCompatActivity; 9 | 10 | import com.gyf.immersionbar.ImmersionBar; 11 | import com.zwl.mybossdemo.R; 12 | 13 | import java.util.Iterator; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | /** 18 | * 筛选 19 | */ 20 | public class FilterActivity extends AppCompatActivity { 21 | private FilterLayout mFilterLayout; 22 | private TextView mFilterResultTV; 23 | private TextView mFilterResultNum; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_filter); 29 | ImmersionBar.with(this).statusBarDarkFont(true).init(); 30 | 31 | mFilterLayout = findViewById(R.id.filter_list); 32 | mFilterResultTV = findViewById(R.id.filter_result_tv); 33 | mFilterResultNum = findViewById(R.id.filter_result_num); 34 | mFilterLayout.setFilterData(FilterDataUtils.getFilterData()); 35 | mFilterLayout.setOnFilterChangeListener(new FilterLayout.OnFilterChangeListener() { 36 | @Override 37 | public void result(Map> result) { 38 | if (result != null) { 39 | Iterator>> iterator = result.entrySet().iterator(); 40 | int num = 0; 41 | while (iterator.hasNext()) { 42 | List value = iterator.next().getValue(); 43 | num += value.size(); 44 | } 45 | if(num==0){ 46 | mFilterResultTV.setText("筛选"); 47 | mFilterResultNum.setVisibility(View.GONE); 48 | }else{ 49 | mFilterResultTV.setText("筛选."); 50 | mFilterResultNum.setVisibility(View.VISIBLE); 51 | mFilterResultNum.setText(String.valueOf(num)); 52 | } 53 | 54 | } 55 | } 56 | }); 57 | } 58 | 59 | 60 | /** 61 | * 重置 62 | * 63 | * @param view 64 | */ 65 | public void reset(View view) { 66 | mFilterLayout.reset(); 67 | } 68 | 69 | /** 70 | * 确定 71 | * 72 | * @param view 73 | */ 74 | public void ok(View view) { 75 | List result = mFilterLayout.result(); 76 | Log.e("FilterActivity", "" + result.toString()); 77 | } 78 | 79 | /** 80 | * 关闭 81 | * 82 | * @param view 83 | */ 84 | public void close(View view) { 85 | finish(); 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zwl/mybossdemo/filter/FilterBean.java: -------------------------------------------------------------------------------- 1 | package com.zwl.mybossdemo.filter; 2 | 3 | /** 4 | * 筛选 5 | * 6 | * @author zwl 7 | * @date on 2020/7/17 8 | */ 9 | public class FilterBean { 10 | public final transient static String UNLIMITED = "-1";//不限,这里与后台定义一致 11 | 12 | public String id; 13 | public String name; 14 | 15 | public FilterBean() { 16 | } 17 | 18 | public FilterBean(String id, String name) { 19 | this.id = id; 20 | this.name = name; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "FilterBean{" + 26 | "id='" + id + '\'' + 27 | ", name='" + name + '\'' + 28 | '}'; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/zwl/mybossdemo/filter/FilterDataUtils.java: -------------------------------------------------------------------------------- 1 | package com.zwl.mybossdemo.filter; 2 | 3 | import com.zwl.mybossdemo.filter.flow.TagFlowLayout; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * @author zwl 10 | * @describe 测试数据 11 | * @date on 2020/7/18 12 | */ 13 | public class FilterDataUtils { 14 | /** 15 | * 这里是测试模拟数据(实际是后台返回) 16 | * 17 | * @return 18 | */ 19 | public static List getFilterData() { 20 | List filterGrops = new ArrayList<>(); 21 | 22 | //学历要求 23 | FilterGrop filterGrop = new FilterGrop(); 24 | filterGrop.gropName = "学历要求"; 25 | filterGrop.key="xlyq"; 26 | filterGrop.filters = new ArrayList<>(); 27 | filterGrop.filters.add(new FilterBean(FilterBean.UNLIMITED, "不限")); 28 | filterGrop.filters.add(new FilterBean("1_1", "初中及以下")); 29 | filterGrop.filters.add(new FilterBean("1_2", "中专/中技")); 30 | filterGrop.filters.add(new FilterBean("1_3", "高中")); 31 | filterGrop.filters.add(new FilterBean("1_4", "大专")); 32 | filterGrop.filters.add(new FilterBean("1_5", "本科")); 33 | filterGrop.filters.add(new FilterBean("1_6", "硕士")); 34 | filterGrop.filters.add(new FilterBean("1_7", "博士")); 35 | filterGrops.add(filterGrop); 36 | 37 | 38 | //薪资待遇 39 | FilterGrop filterGrop1 = new FilterGrop(); 40 | filterGrop1.gropName = "薪资待遇"; 41 | filterGrop1.key="xzdy"; 42 | filterGrop1.filterType = TagFlowLayout.TAG_MODE_SINGLE; 43 | filterGrop1.filters = new ArrayList<>(); 44 | filterGrop1.filters.add(new FilterBean(FilterBean.UNLIMITED, "不限")); 45 | filterGrop1.filters.add(new FilterBean("2_1", "3K以下")); 46 | filterGrop1.filters.add(new FilterBean("2_2", "3-5K")); 47 | filterGrop1.filters.add(new FilterBean("2_3", "5-10K")); 48 | filterGrop1.filters.add(new FilterBean("2_4", "10-20K")); 49 | filterGrop1.filters.add(new FilterBean("2_5", "20-50K")); 50 | filterGrop1.filters.add(new FilterBean("2_6", "50K以上")); 51 | filterGrops.add(filterGrop1); 52 | 53 | 54 | //经验要求 55 | FilterGrop filterGrop2 = new FilterGrop(); 56 | filterGrop2.gropName = "经验要求"; 57 | filterGrop2.key="jyyq"; 58 | filterGrop2.filters = new ArrayList<>(); 59 | filterGrop2.filters.add(new FilterBean(FilterBean.UNLIMITED, "不限")); 60 | filterGrop2.filters.add(new FilterBean("3_1", "在校生")); 61 | filterGrop2.filters.add(new FilterBean("3_2", "应届生")); 62 | filterGrop2.filters.add(new FilterBean("3_3", "1年以内")); 63 | filterGrop2.filters.add(new FilterBean("3_4", "1-3年")); 64 | filterGrop2.filters.add(new FilterBean("3_5", "3-5年")); 65 | filterGrop2.filters.add(new FilterBean("3_6", "5-10年")); 66 | filterGrop2.filters.add(new FilterBean("3_7", "10年以上")); 67 | filterGrops.add(filterGrop2); 68 | 69 | 70 | //行业分类 71 | FilterGrop filterGrop3 = new FilterGrop(); 72 | filterGrop3.gropName = "行业分类"; 73 | filterGrop3.key="hyfl"; 74 | filterGrop3.filters = new ArrayList<>(); 75 | filterGrop3.filters.add(new FilterBean(FilterBean.UNLIMITED, "不限")); 76 | filterGrop3.filters.add(new FilterBean("4_1", "电子商务")); 77 | filterGrop3.filters.add(new FilterBean("4_2", "游戏")); 78 | filterGrop3.filters.add(new FilterBean("4_3", "媒体")); 79 | filterGrop3.filters.add(new FilterBean("4_4", "广告营销")); 80 | filterGrop3.filters.add(new FilterBean("4_5", "数据服务")); 81 | filterGrop3.filters.add(new FilterBean("4_6", "医疗健康")); 82 | filterGrop3.filters.add(new FilterBean("4_7", "生活服务")); 83 | filterGrop3.filters.add(new FilterBean("4_8", "O2O")); 84 | filterGrop3.filters.add(new FilterBean("4_9", "旅游")); 85 | filterGrop3.filters.add(new FilterBean("4_10", "分类信息")); 86 | filterGrop3.filters.add(new FilterBean("4_11", "音乐/视频/阅读")); 87 | filterGrop3.filters.add(new FilterBean("4_12", "在线教育")); 88 | filterGrop3.filters.add(new FilterBean("4_13", "社交网络")); 89 | filterGrop3.filters.add(new FilterBean("4_14", "人力资源服务")); 90 | filterGrop3.filters.add(new FilterBean("4_15", "企业服务")); 91 | filterGrop3.filters.add(new FilterBean("4_16", "信息安全")); 92 | filterGrop3.filters.add(new FilterBean("4_17", "智能硬件")); 93 | filterGrop3.filters.add(new FilterBean("4_18", "移动互联网")); 94 | filterGrop3.filters.add(new FilterBean("4_19", "互联网")); 95 | filterGrop3.filters.add(new FilterBean("4_20", "计算机软件")); 96 | filterGrop3.filters.add(new FilterBean("4_21", "通信/网络设备")); 97 | filterGrop3.filters.add(new FilterBean("4_22", "广告/公关/会展")); 98 | filterGrop3.filters.add(new FilterBean("4_23", "互联网金融")); 99 | filterGrop3.filters.add(new FilterBean("4_24", "物流/仓储")); 100 | filterGrop3.filters.add(new FilterBean("4_25", "贸易/进出口")); 101 | filterGrop3.filters.add(new FilterBean("4_26", "咨询")); 102 | filterGrop3.filters.add(new FilterBean("4_27", "工程施工")); 103 | filterGrop3.filters.add(new FilterBean("4_28", "汽车生产")); 104 | filterGrop3.filters.add(new FilterBean("4_29", "其他行业")); 105 | filterGrops.add(filterGrop3); 106 | 107 | 108 | //公司规模 109 | FilterGrop filterGrop4 = new FilterGrop(); 110 | filterGrop4.gropName = "公司规模"; 111 | filterGrop4.key="gsgm"; 112 | filterGrop4.filters = new ArrayList<>(); 113 | filterGrop4.filters.add(new FilterBean(FilterBean.UNLIMITED, "不限")); 114 | filterGrop4.filters.add(new FilterBean("4_1", "0-20人")); 115 | filterGrop4.filters.add(new FilterBean("4_2", "20-99人")); 116 | filterGrop4.filters.add(new FilterBean("4_3", "100-499人")); 117 | filterGrop4.filters.add(new FilterBean("4_4", "500-999人")); 118 | filterGrop4.filters.add(new FilterBean("4_5", "1000-9999人")); 119 | filterGrop4.filters.add(new FilterBean("4_6", "10000人以上")); 120 | filterGrops.add(filterGrop4); 121 | 122 | 123 | //融资阶段 124 | FilterGrop filterGrop5 = new FilterGrop(); 125 | filterGrop5.gropName = "融资阶段"; 126 | filterGrop5.key="rzjd"; 127 | filterGrop5.filters = new ArrayList<>(); 128 | filterGrop5.filters.add(new FilterBean(FilterBean.UNLIMITED, "不限")); 129 | filterGrop5.filters.add(new FilterBean("5_1", "未融资")); 130 | filterGrop5.filters.add(new FilterBean("5_2", "天使轮")); 131 | filterGrop5.filters.add(new FilterBean("5_3", "A轮")); 132 | filterGrop5.filters.add(new FilterBean("5_4", "B轮")); 133 | filterGrop5.filters.add(new FilterBean("5_5", "C轮")); 134 | filterGrop5.filters.add(new FilterBean("5_6", "D轮及以上")); 135 | filterGrop5.filters.add(new FilterBean("5_7", "已上市")); 136 | filterGrop5.filters.add(new FilterBean("5_8", "不需要融资")); 137 | filterGrops.add(filterGrop5); 138 | return filterGrops; 139 | 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /app/src/main/java/com/zwl/mybossdemo/filter/FilterGrop.java: -------------------------------------------------------------------------------- 1 | package com.zwl.mybossdemo.filter; 2 | 3 | import com.zwl.mybossdemo.filter.flow.TagFlowLayout; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author zwl 9 | * @describe 筛选 组 10 | * @date on 2020/7/17 11 | */ 12 | public class FilterGrop { 13 | public String gropName; 14 | public String key;//请求数据的key 15 | public int filterType = TagFlowLayout.TAG_MODE_MULTIPLE; 16 | public List filters; 17 | 18 | 19 | @Override 20 | public String toString() { 21 | return "FilterGrop{" + 22 | "gropName='" + gropName + '\'' + 23 | ", filters=" + filters + 24 | '}'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/zwl/mybossdemo/filter/FilterLayout.java: -------------------------------------------------------------------------------- 1 | package com.zwl.mybossdemo.filter; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.Gravity; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ImageView; 10 | import android.widget.LinearLayout; 11 | import android.widget.TextView; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | import androidx.core.widget.NestedScrollView; 16 | 17 | import com.zwl.mybossdemo.R; 18 | import com.zwl.mybossdemo.filter.flow.FlowLayout; 19 | import com.zwl.mybossdemo.filter.flow.TagFlowAdapter; 20 | import com.zwl.mybossdemo.filter.flow.TagFlowContainer; 21 | import com.zwl.mybossdemo.filter.flow.TagFlowLayout; 22 | 23 | import java.util.ArrayList; 24 | import java.util.HashMap; 25 | import java.util.List; 26 | import java.util.Map; 27 | 28 | /** 29 | * @author zwl 30 | * @date on 2020/7/18 31 | */ 32 | public class FilterLayout extends NestedScrollView { 33 | 34 | private final LinearLayout parantRoot; 35 | 36 | private Map> filterBeans = new HashMap<>(); 37 | 38 | public FilterLayout(@NonNull Context context) { 39 | this(context, null); 40 | } 41 | 42 | public FilterLayout(@NonNull Context context, @Nullable AttributeSet attrs) { 43 | this(context, attrs, 0); 44 | } 45 | 46 | public FilterLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 47 | super(context, attrs, defStyleAttr); 48 | parantRoot = new LinearLayout(context); 49 | parantRoot.setOrientation(LinearLayout.VERTICAL); 50 | parantRoot.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 51 | addView(parantRoot); 52 | } 53 | 54 | public void setFilterData(List filterData) { 55 | parantRoot.removeAllViews(); 56 | filterBeans.clear(); 57 | for (int i = 0; i < filterData.size(); i++) { 58 | View itemView = LayoutInflater.from(getContext()).inflate(R.layout.item_filter_view, null); 59 | TextView filterName = itemView.findViewById(R.id.filter_name); 60 | final TagFlowLayout filterList = itemView.findViewById(R.id.filter_flow); 61 | final ImageView filterMore = itemView.findViewById(R.id.filter_more); 62 | final FilterGrop filterGrop = filterData.get(i); 63 | if (filterGrop != null) { 64 | filterName.setText(filterGrop.gropName); 65 | filterList.setTagMode(filterGrop.filterType); 66 | filterList.setTagAdapter(new TagFlowAdapter(filterGrop.filters, FilterBean.UNLIMITED, new String[]{FilterBean.UNLIMITED}) { 67 | @Override 68 | public View getView(TagFlowContainer parent, FilterBean item, int position) { 69 | TextView textView = (TextView) LayoutInflater.from(parent.getContext()).inflate(R.layout.item_filter_tag, parent, false); 70 | textView.setText("" + getItem(position).name); 71 | textView.setGravity(Gravity.CENTER); 72 | return textView; 73 | } 74 | 75 | @Override 76 | public String isCheckContent(FilterBean item, int position) { 77 | return item.id; 78 | } 79 | }); 80 | 81 | filterList.setOnFoldChangedListener(new FlowLayout.OnFoldChangedListener() { 82 | @Override 83 | public void onFoldChanged(boolean canFold, boolean fold) { 84 | filterMore.setVisibility(canFold ? View.VISIBLE : View.GONE); 85 | filterMore.setImageResource(fold ? R.drawable.boss_icon_down : R.drawable.boss_icon_up); 86 | } 87 | 88 | @Override 89 | public void onFoldChanging(boolean canFold, boolean fold) { 90 | } 91 | }); 92 | filterMore.setOnClickListener(new View.OnClickListener() { 93 | @Override 94 | public void onClick(View v) { 95 | filterList.toggleFold(); 96 | } 97 | }); 98 | 99 | filterList.setOnCheckChangeListener(new TagFlowLayout.OnCheckChangeListener() { 100 | @Override 101 | public void onCheckChange(boolean isCheck, int position) { 102 | filterBeans.put(filterGrop.key, filterList.getCheckedItemsFilter()); 103 | if (listener != null) { 104 | listener.result(filterBeans); 105 | } 106 | } 107 | }); 108 | } 109 | parantRoot.addView(itemView); 110 | } 111 | } 112 | 113 | 114 | public void reset() { 115 | int count = parantRoot.getChildCount(); 116 | for (int i = 0; i < count; i++) { 117 | View childAt = parantRoot.getChildAt(i); 118 | TagFlowLayout tagFlowLayout = childAt.findViewById(R.id.filter_flow); 119 | tagFlowLayout.reset(); 120 | } 121 | if (listener != null) { 122 | filterBeans.clear(); 123 | listener.result(filterBeans); 124 | } 125 | } 126 | 127 | public List result() { 128 | int count = parantRoot.getChildCount(); 129 | List list = new ArrayList(); 130 | for (int i = 0; i < count; i++) { 131 | View childAt = parantRoot.getChildAt(i); 132 | TagFlowLayout tagFlowLayout = childAt.findViewById(R.id.filter_flow); 133 | list.addAll(tagFlowLayout.getCheckedItems()); 134 | } 135 | return list; 136 | } 137 | 138 | OnFilterChangeListener listener; 139 | 140 | public void setOnFilterChangeListener(OnFilterChangeListener listener) { 141 | this.listener = listener; 142 | } 143 | 144 | interface OnFilterChangeListener { 145 | void result(Map> result); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /app/src/main/java/com/zwl/mybossdemo/filter/flow/FlowLayout.java: -------------------------------------------------------------------------------- 1 | package com.zwl.mybossdemo.filter.flow; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.zwl.mybossdemo.R; 10 | 11 | /** 12 | * 流布局 13 | * 1、是否折叠 14 | * 2、折叠行数 15 | * 3、对其方式 16 | * 4、平均宽度及数量 17 | * 5、水平竖直间隔 18 | */ 19 | public class FlowLayout extends ViewGroup { 20 | 21 | //默认折叠状态 22 | private static final boolean DEFAULT_FOLD = false; 23 | //折叠的行数 24 | private static final int DEFAULT_FOLD_LINES = 1; 25 | //左对齐 26 | private static final int DEFAULT_GRAVITY_LEFT = 0; 27 | //右对齐 28 | private static final int DEFAULT_GRAVITY_RIGHT = 1; 29 | 30 | //是否折叠,默认false不折叠 31 | private boolean mFold; 32 | //折叠行数 33 | private int mFoldLines = 1; 34 | //对齐 默认左对齐 35 | private int mGravity = DEFAULT_GRAVITY_LEFT; 36 | //折叠状态 37 | private Boolean mFoldState; 38 | //是否平均 39 | private boolean mEqually; 40 | //一行平局数量 41 | private int mEquallyCount; 42 | //水平距离 43 | private int mHorizontalSpacing; 44 | //竖直距离 45 | private int mVerticalSpacing; 46 | 47 | private OnFoldChangedListener mOnFoldChangedListener; 48 | 49 | public FlowLayout(Context context) { 50 | this(context, null); 51 | } 52 | 53 | public FlowLayout(Context context, AttributeSet attrs) { 54 | this(context, attrs, 0); 55 | } 56 | 57 | public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) { 58 | super(context, attrs, defStyleAttr); 59 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout); 60 | mFold = a.getBoolean(R.styleable.FlowLayout_flow_fold, DEFAULT_FOLD); 61 | mFoldLines = a.getInt(R.styleable.FlowLayout_flow_foldLines, DEFAULT_FOLD_LINES); 62 | mGravity = a.getInt(R.styleable.FlowLayout_flow_gravity, DEFAULT_GRAVITY_LEFT); 63 | mEqually = a.getBoolean(R.styleable.FlowLayout_flow_equally, true); 64 | mEquallyCount = a.getInt(R.styleable.FlowLayout_flow_equally_count, 0); 65 | mHorizontalSpacing = a.getDimensionPixelOffset(R.styleable.FlowLayout_flow_horizontalSpacing, dp2px(4)); 66 | mVerticalSpacing = a.getDimensionPixelOffset(R.styleable.FlowLayout_flow_verticalSpacing, dp2px(4)); 67 | a.recycle(); 68 | } 69 | 70 | 71 | @Override 72 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 73 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 74 | //当设置折叠 折叠数设置小于0直接隐藏布局 75 | if (mFold && mFoldLines <= 0) { 76 | setVisibility(GONE); 77 | changeFold(true, true); 78 | return; 79 | } 80 | //获取mode 和 size 81 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 82 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 83 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); 84 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 85 | 86 | final int layoutWidth = widthSize - getPaddingLeft() - getPaddingRight(); 87 | //判断如果布局宽度抛去左右padding小于0,也不能处理了 88 | if (layoutWidth <= 0) { 89 | return; 90 | } 91 | 92 | //这里默认宽高默认值默认把左右,上下padding加上 93 | int width = getPaddingLeft() + getPaddingRight(); 94 | int height = getPaddingTop() + getPaddingBottom(); 95 | 96 | //初始一行的宽度 97 | int lineWidth = 0; 98 | //初始一行的高度 99 | int lineHeight = 0; 100 | 101 | //测量子View 102 | measureChildren(widthMeasureSpec, heightMeasureSpec); 103 | 104 | int[] wh = null; 105 | int childWidth, childHeight; 106 | int childWidthMeasureSpec = 0, childHeightMeasureSpec = 0; 107 | //行数 108 | int line = 0; 109 | //折叠的状态 110 | boolean newFoldState = false; 111 | int count = getChildCount(); 112 | for (int i = 0; i < count; i++) { 113 | final View view = getChildAt(i); 114 | //这里需要先判断子view是否被设置了GONE 115 | if (view.getVisibility() == GONE) { 116 | continue; 117 | } 118 | 119 | //如果设置是平局显示 120 | if (mEqually) { 121 | //这里只要计算一次就可以了 122 | if (wh == null) { 123 | //取子view最大的宽高 124 | wh = getMaxWH(); 125 | //求一行能显示多少个 126 | int oneRowItemCount = (layoutWidth + mHorizontalSpacing) / (mHorizontalSpacing + wh[0]); 127 | //当你设置了一行平局显示多少个 128 | if (mEquallyCount > 0) { 129 | //判断当你设定的数量小于计算的数量时,使用设置的,所以说当我们计算的竖直小于设置的值的时候这里并没有强制设置设定的值 130 | //如果需求要求必须按照设定的来,这里就不要做if判断,直接使用设定的值,但是布局显示会出现显示不全或者...的情况。 131 | if (oneRowItemCount > mEquallyCount) { 132 | oneRowItemCount = mEquallyCount; 133 | } 134 | } 135 | // 根据上面计算的一行显示的数量来计算一个的宽度 136 | int newWidth = (layoutWidth - (oneRowItemCount - 1) * mHorizontalSpacing) / oneRowItemCount; 137 | wh[0] = newWidth; 138 | //重新获取子view的MeasureSpec 139 | childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(wh[0], MeasureSpec.EXACTLY); 140 | childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(wh[1], MeasureSpec.EXACTLY); 141 | } 142 | childWidth = wh[0]; 143 | childHeight = wh[1]; 144 | //重新测量子view的大小 145 | getChildAt(i).measure(childWidthMeasureSpec, childHeightMeasureSpec); 146 | } 147 | // 自适显示 148 | else { 149 | childWidth = view.getMeasuredWidth(); 150 | childHeight = view.getMeasuredHeight(); 151 | } 152 | if (i == 0) {//第一行 153 | lineWidth = getPaddingLeft() + getPaddingRight() + childWidth; 154 | lineHeight = childHeight; 155 | } else { 156 | //判断是否需要换行 157 | //换行 158 | if (lineWidth + mHorizontalSpacing + childWidth > widthSize) { 159 | line++;//行数增加 160 | width = Math.max(lineWidth, width);// 取最大的宽度 161 | //这里判断是否设置折叠及行数是否超过了设定值 162 | if (mFold && line >= mFoldLines) { 163 | line++; 164 | height += lineHeight; 165 | newFoldState = true; 166 | break; 167 | } 168 | //重新开启新行,开始记录 169 | lineWidth = getPaddingLeft() + getPaddingRight() + childWidth; 170 | //叠加当前高度, 171 | height += mVerticalSpacing + lineHeight; 172 | //开启记录下一行的高度 173 | lineHeight = childHeight; 174 | } 175 | //不换行 176 | else { 177 | lineWidth = lineWidth + mHorizontalSpacing + childWidth; 178 | lineHeight = Math.max(lineHeight, childHeight); 179 | } 180 | } 181 | // 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较 182 | if (i == count - 1) { 183 | line++; 184 | width = Math.max(width, lineWidth); 185 | height += lineHeight; 186 | } 187 | } 188 | //根据计算的值重新设置 189 | setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width, 190 | heightMode == MeasureSpec.EXACTLY ? heightSize : height); 191 | //折叠状态 192 | changeFold(line > mFoldLines, newFoldState); 193 | } 194 | 195 | 196 | @Override 197 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 198 | final int layoutWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); 199 | if (layoutWidth <= 0) { 200 | return; 201 | } 202 | int childWidth, childHeight; 203 | //需要加上top padding 204 | int top = getPaddingTop(); 205 | final int[] wh = getMaxWH(); 206 | int lineHeight = 0; 207 | int line = 0; 208 | 209 | //左对齐 210 | if (mGravity == DEFAULT_GRAVITY_LEFT) { 211 | //左侧需要先加上左边的padding 212 | int left = getPaddingLeft(); 213 | for (int i = 0, count = getChildCount(); i < count; i++) { 214 | final View view = getChildAt(i); 215 | //这里一样判断下显示状态 216 | if (view.getVisibility() == GONE) { 217 | continue; 218 | } 219 | //如果设置的平均 就使用最大的宽度和高度 否则直接自适宽高 220 | if (mEqually) { 221 | childWidth = wh[0]; 222 | childHeight = wh[1]; 223 | } else { 224 | childWidth = view.getMeasuredWidth(); 225 | childHeight = view.getMeasuredHeight(); 226 | } 227 | //第一行开始摆放 228 | if (i == 0) { 229 | view.layout(left, top, left + childWidth, top + childHeight); 230 | lineHeight = childHeight; 231 | } else { 232 | //判断是否需要换行 233 | if (left + mHorizontalSpacing + childWidth > layoutWidth + getPaddingLeft()) { 234 | line++; 235 | if (mFold && line >= mFoldLines) { 236 | break; 237 | } 238 | //重新起行 239 | left = getPaddingLeft(); 240 | top = top + mVerticalSpacing + lineHeight; 241 | lineHeight = childHeight; 242 | } else { 243 | left = left + mHorizontalSpacing; 244 | lineHeight = Math.max(lineHeight, childHeight); 245 | } 246 | view.layout(left, top, left + childWidth, top + childHeight); 247 | } 248 | //累加left 249 | left += childWidth; 250 | } 251 | } 252 | //右对齐 253 | else { 254 | int paddingLeft = getPaddingLeft(); 255 | int right = layoutWidth + paddingLeft;// 相当于getMeasuredWidth() - getPaddingRight(); 256 | 257 | for (int i = 0, count = getChildCount(); i < count; i++) { 258 | final View view = getChildAt(i); 259 | if (view.getVisibility() == GONE) { 260 | continue; 261 | } 262 | //如果设置的平均 就使用最大的宽度和高度 否则直接自适宽高 263 | if (mEqually) { 264 | childWidth = wh[0]; 265 | childHeight = wh[1]; 266 | } else { 267 | childWidth = view.getMeasuredWidth(); 268 | childHeight = view.getMeasuredHeight(); 269 | } 270 | if (i == 0) { 271 | view.layout(right - childWidth, top, right, top + childHeight); 272 | lineHeight = childHeight; 273 | } else { 274 | //判断是否需要换行 275 | if (right - childWidth - mHorizontalSpacing < paddingLeft) { 276 | line++; 277 | if (mFold && line >= mFoldLines) { 278 | break; 279 | } 280 | //重新起行 281 | right = layoutWidth + paddingLeft; 282 | top = top + mVerticalSpacing + lineHeight; 283 | lineHeight = childHeight; 284 | } else { 285 | right = right - mHorizontalSpacing; 286 | lineHeight = Math.max(lineHeight, childHeight); 287 | } 288 | view.layout(right - childWidth, top, right, top + childHeight); 289 | } 290 | right -= childWidth; 291 | } 292 | } 293 | 294 | 295 | } 296 | 297 | /** 298 | * 取最大的子view的宽度和高度 299 | * 300 | * @return 301 | */ 302 | private int[] getMaxWH() { 303 | int maxWidth = 0; 304 | int maxHeight = 0; 305 | for (int i = 0, count = getChildCount(); i < count; i++) { 306 | final View view = getChildAt(i); 307 | if (view.getVisibility() == GONE) { 308 | continue; 309 | } 310 | maxWidth = Math.max(maxWidth, view.getMeasuredWidth()); 311 | maxHeight = Math.max(maxHeight, view.getMeasuredHeight()); 312 | } 313 | return new int[]{maxWidth, maxHeight}; 314 | } 315 | 316 | /** 317 | * 折叠状态改变回调 318 | * 319 | * @param canFold 320 | * @param newFoldState 321 | */ 322 | private void changeFold(boolean canFold, boolean newFoldState) { 323 | if (mFoldState == null || mFoldState != newFoldState) { 324 | if (canFold) { 325 | mFoldState = newFoldState; 326 | } 327 | if (mOnFoldChangedListener != null) { 328 | mOnFoldChangedListener.onFoldChanged(canFold, newFoldState); 329 | } 330 | } 331 | if (mOnFoldChangedListener != null) { 332 | mOnFoldChangedListener.onFoldChanging(canFold, newFoldState); 333 | } 334 | } 335 | 336 | /** 337 | * 设置是否折叠 338 | * 339 | * @param fold 340 | */ 341 | public void setFold(boolean fold) { 342 | mFold = fold; 343 | if (mFoldLines <= 0) { 344 | setVisibility(fold ? GONE : VISIBLE); 345 | changeFold(true, fold); 346 | } else { 347 | requestLayout(); 348 | } 349 | } 350 | 351 | /** 352 | * 折叠切换,如果之前是折叠状态就切换为未折叠状态,否则相反 353 | */ 354 | public void toggleFold() { 355 | setFold(!mFold); 356 | } 357 | 358 | 359 | /** 360 | * dp->px 361 | * 362 | * @param dp 363 | * @return 364 | */ 365 | private int dp2px(int dp) { 366 | return (int) (getContext().getResources().getDisplayMetrics().density * dp); 367 | } 368 | 369 | 370 | /** 371 | * 设置折叠状态回调 372 | * 373 | * @param listener 374 | */ 375 | public void setOnFoldChangedListener(OnFoldChangedListener listener) { 376 | mOnFoldChangedListener = listener; 377 | } 378 | 379 | public interface OnFoldChangedListener { 380 | /** 381 | * 折叠状态回调 382 | * 383 | * @param canFold 是否可以折叠,true为可以折叠,false为不可以折叠 384 | * @param fold 当前折叠状态,true为折叠,false为未折叠 385 | */ 386 | void onFoldChanged(boolean canFold, boolean fold); 387 | 388 | 389 | /** 390 | * 折叠状态时时回调 391 | * 392 | * @param canFold 是否可以折叠,true为可以折叠,false为不可以折叠 393 | * @param fold 当前折叠状态,true为折叠,false为未折叠 394 | */ 395 | void onFoldChanging(boolean canFold, boolean fold); 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /app/src/main/java/com/zwl/mybossdemo/filter/flow/TagFlowAdapter.java: -------------------------------------------------------------------------------- 1 | package com.zwl.mybossdemo.filter.flow; 2 | 3 | import android.view.View; 4 | 5 | import java.util.HashSet; 6 | import java.util.List; 7 | 8 | /** 9 | * @author zwl 10 | * @describe tag 适配器 11 | * @date on 2020/6/26 12 | */ 13 | public abstract class TagFlowAdapter { 14 | private List datas; 15 | 16 | private HashSet checkedList = new HashSet(); 17 | 18 | private S mutexCheck; 19 | 20 | private OnDataChangedListener onDataChangedListener; 21 | 22 | public TagFlowAdapter() { 23 | } 24 | 25 | /** 26 | * 构造 27 | * @param datas 数据 28 | */ 29 | public TagFlowAdapter(List datas) { 30 | this.datas = datas; 31 | } 32 | 33 | /** 34 | * 构造 35 | * @param datas 数据 36 | * @param checkes 选中数据 37 | */ 38 | public TagFlowAdapter(List datas, S... checkes) { 39 | this.datas = datas; 40 | addChecked(checkes); 41 | } 42 | 43 | /** 44 | * 构造 45 | * @param datas 数据 46 | * @param mutexCheck 互斥数据(不限)设置后这个数据就和其他数据互斥 47 | */ 48 | public TagFlowAdapter(List datas, S mutexCheck) { 49 | this.datas = datas; 50 | setMutexCheck(mutexCheck); 51 | } 52 | 53 | /** 54 | * 构造 55 | * @param datas 数据 56 | * @param mutexCheck 互斥数据(不限)设置后这个数据就和其他数据互斥 57 | * @param checkes 选中数据 58 | */ 59 | public TagFlowAdapter(List datas, S mutexCheck, S... checkes) { 60 | this.datas = datas; 61 | setMutexCheck(mutexCheck); 62 | addChecked(checkes); 63 | } 64 | 65 | /** 66 | * 设置互斥数据 67 | * @param mutexCheck 68 | */ 69 | public void setMutexCheck(S mutexCheck) { 70 | this.mutexCheck = mutexCheck; 71 | } 72 | 73 | 74 | /** 75 | * 添加选中数据 76 | * @param checkes 77 | */ 78 | public void addChecked(S... checkes) { 79 | for (S s : checkes) { 80 | checkedList.add(s); 81 | } 82 | } 83 | 84 | /** 85 | * 判断数据是否被选中 86 | * @param s 87 | * @return 88 | */ 89 | public boolean isChecked(S s) { 90 | return checkedList.contains(s); 91 | } 92 | 93 | /** 94 | * 数据的数量 95 | * @return 96 | */ 97 | public int getCount() { 98 | return this.datas == null ? 0 : this.datas.size(); 99 | } 100 | 101 | /** 102 | * 获取数据 103 | * @return 104 | */ 105 | public List getDatas() { 106 | return datas; 107 | } 108 | 109 | /** 110 | * 获取指定位置的数据 111 | * @param position 112 | * @return 113 | */ 114 | public T getItem(int position) { 115 | return this.datas.get(position); 116 | } 117 | 118 | /** 119 | * 获取互斥(不限)数据 120 | * @return 121 | */ 122 | public S getMutexCheck() { 123 | return mutexCheck; 124 | } 125 | 126 | /** 127 | * 获取当前选中的数据 128 | * @return 129 | */ 130 | public HashSet getCheckedList() { 131 | return checkedList; 132 | } 133 | 134 | /** 135 | * 移除选中数据 136 | * @param o 137 | * @return 138 | */ 139 | public boolean removeChecked(S o) { 140 | boolean b = checkedList.remove(o); 141 | return b; 142 | } 143 | 144 | /** 145 | * 移除互斥数据 146 | */ 147 | public void removeMutexCheck() { 148 | if (mutexCheck != null && containsChecked(mutexCheck)) { 149 | checkedList.remove(mutexCheck); 150 | } 151 | } 152 | 153 | /** 154 | * 移除选中数据 155 | * @param checkes 156 | */ 157 | public void removeChecked(S... checkes) { 158 | for (S s : checkes) { 159 | checkedList.remove(s); 160 | } 161 | } 162 | 163 | /** 164 | * 选中数据会否包含数据 165 | * @param s 166 | * @return 167 | */ 168 | public boolean containsChecked(S s) { 169 | return checkedList.contains(s); 170 | } 171 | 172 | 173 | /** 174 | * 移除所有数据 175 | */ 176 | public void removeAllChecked() { 177 | checkedList.clear(); 178 | } 179 | 180 | public void setCheckedList(HashSet checkedList) { 181 | this.checkedList = checkedList; 182 | } 183 | 184 | 185 | public abstract View getView(TagFlowContainer parent, T item, int position); 186 | 187 | public abstract S isCheckContent(T item, int position); 188 | 189 | public void notifyDataChanged() { 190 | if (this.onDataChangedListener != null) { 191 | this.onDataChangedListener.onChanged(); 192 | } 193 | 194 | } 195 | 196 | void setOnDataChangedListener(OnDataChangedListener listener) { 197 | this.onDataChangedListener = listener; 198 | } 199 | 200 | interface OnDataChangedListener { 201 | void onChanged(); 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /app/src/main/java/com/zwl/mybossdemo/filter/flow/TagFlowAdapter2.java: -------------------------------------------------------------------------------- 1 | package com.zwl.mybossdemo.filter.flow; 2 | 3 | /** 4 | * @author zwl 5 | * @date on 2020/7/18 6 | */ 7 | public abstract class TagFlowAdapter2 extends TagFlowAdapter { 8 | @Override 9 | public Integer isCheckContent(T item, int position) { 10 | return position; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/zwl/mybossdemo/filter/flow/TagFlowContainer.java: -------------------------------------------------------------------------------- 1 | package com.zwl.mybossdemo.filter.flow; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.widget.Checkable; 7 | import android.widget.FrameLayout; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.annotation.Nullable; 11 | 12 | /** 13 | * @author zwl 14 | * @describe tag标签容器(处理选中效果) 15 | * @date on 2020/6/26 16 | */ 17 | public class TagFlowContainer extends FrameLayout implements Checkable { 18 | 19 | private boolean isChecked; 20 | private static final int[] CHECK_STATE = new int[]{android.R.attr.state_checked}; 21 | 22 | public TagFlowContainer(@NonNull Context context) { 23 | this(context, null); 24 | } 25 | 26 | public TagFlowContainer(@NonNull Context context, @Nullable AttributeSet attrs) { 27 | this(context, attrs, 0); 28 | } 29 | 30 | public TagFlowContainer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 31 | super(context, attrs, defStyleAttr); 32 | } 33 | 34 | 35 | public View getTagView() { 36 | return getChildAt(0); 37 | } 38 | 39 | @Override 40 | protected int[] onCreateDrawableState(int extraSpace) { 41 | int[] states = super.onCreateDrawableState(extraSpace + 1); 42 | if (isChecked()) { 43 | mergeDrawableStates(states, CHECK_STATE); 44 | } 45 | return states; 46 | } 47 | 48 | @Override 49 | public void setChecked(boolean checked) { 50 | if (this.isChecked != checked) { 51 | this.isChecked = checked; 52 | refreshDrawableState(); 53 | } 54 | } 55 | 56 | @Override 57 | public boolean isChecked() { 58 | return isChecked; 59 | } 60 | 61 | @Override 62 | public void toggle() { 63 | setChecked(!isChecked); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/zwl/mybossdemo/filter/flow/TagFlowLayout.java: -------------------------------------------------------------------------------- 1 | package com.zwl.mybossdemo.filter.flow; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.zwl.mybossdemo.R; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * @author zwl 16 | * @describe TAG 布局 17 | * @date on 2020/6/26 18 | */ 19 | public class TagFlowLayout extends FlowLayout implements TagFlowAdapter.OnDataChangedListener { 20 | 21 | public final static int TAG_MODE_SHOW = 0; //标签 22 | public final static int TAG_MODE_SINGLE = 1;//单选 23 | public final static int TAG_MODE_MULTIPLE = 2;//多选 24 | private int mTagMode = TAG_MODE_SHOW; 25 | 26 | private Context mContext; 27 | 28 | private TagFlowAdapter tagAdapter; 29 | 30 | private OnCheckChangeListener onCheckChangeListener; 31 | private OnTagClickListener onTagClickListener; 32 | private TagFlowContainer mutexTagFlowView;//互斥选项 33 | 34 | public TagFlowLayout(Context context) { 35 | this(context, null); 36 | } 37 | 38 | public TagFlowLayout(Context context, AttributeSet attrs) { 39 | super(context, attrs); 40 | this.mContext = context; 41 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TagFlowLayout); 42 | mTagMode = typedArray.getInt(R.styleable.TagFlowLayout_flow_mode, TAG_MODE_SHOW); 43 | typedArray.recycle(); 44 | } 45 | 46 | 47 | public void setTagAdapter(TagFlowAdapter tagAdapter) { 48 | this.tagAdapter = tagAdapter; 49 | this.tagAdapter.setOnDataChangedListener(this); 50 | updateTagView(); 51 | } 52 | 53 | /** 54 | * 设置模式(单选,标签,多选) 55 | * 56 | * @param mTagMode 57 | */ 58 | public void setTagMode(int mTagMode) { 59 | this.mTagMode = mTagMode; 60 | } 61 | 62 | @Override 63 | public void onChanged() { 64 | updateTagView(); 65 | } 66 | 67 | 68 | private void updateTagView() { 69 | removeAllViews(); 70 | if (null == tagAdapter) return; 71 | int count = tagAdapter.getCount(); 72 | 73 | TagFlowContainer tagContainer; 74 | for (int i = 0; i < count; i++) { 75 | tagContainer = new TagFlowContainer(mContext); 76 | View tagView = tagAdapter.getView(tagContainer, tagAdapter.getItem(i), i); 77 | //允许我们的CHECKED状态向下传递 78 | tagView.setDuplicateParentStateEnabled(true); 79 | tagContainer.addView(tagView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 80 | tagContainer.setTag(tagAdapter.isCheckContent(tagAdapter.getItem(i), i)); 81 | tagContainer.setChecked(tagAdapter.isChecked(tagAdapter.isCheckContent(tagAdapter.getItem(i), i))); 82 | tagContainer.setOnClickListener(new TagClickListener(i)); 83 | addView(tagContainer); 84 | } 85 | mutexTagFlowView = getViewByTag(tagAdapter.getMutexCheck()); 86 | } 87 | 88 | 89 | /** 90 | * 设置互斥选中还是不选中 91 | * 92 | * @param isCheck 93 | */ 94 | public void setMutexTagFlowView(boolean isCheck) { 95 | if (tagAdapter.getMutexCheck() != null && mutexTagFlowView != null) { 96 | if (isCheck) { 97 | tagAdapter.addChecked(tagAdapter.getMutexCheck()); 98 | } else { 99 | tagAdapter.removeMutexCheck(); 100 | } 101 | mutexTagFlowView.setChecked(isCheck); 102 | } 103 | } 104 | 105 | 106 | /** 107 | * 根据tag获取对应的View 108 | * 109 | * @param o 110 | * @return 111 | */ 112 | public TagFlowContainer getViewByTag(Object o) { 113 | if (o == null) return null; 114 | int count = tagAdapter.getCount(); 115 | for (int i = 0; i < count; i++) { 116 | TagFlowContainer childAt = (TagFlowContainer) getChildAt(i); 117 | if (childAt.getTag() != null && childAt.getTag() == o) { 118 | return childAt; 119 | } 120 | } 121 | return null; 122 | } 123 | 124 | 125 | /** 126 | * 点击事件处理 127 | */ 128 | private class TagClickListener implements View.OnClickListener { 129 | private int position; 130 | 131 | public TagClickListener(int i) { 132 | this.position = i; 133 | } 134 | 135 | @Override 136 | public void onClick(View v) { 137 | TagFlowContainer tagFlowContainer = (TagFlowContainer) v; 138 | if (onTagClickListener != null) { 139 | if (onTagClickListener.onTagClick(tagFlowContainer, position)) { 140 | return; 141 | } 142 | } 143 | //单选 144 | if (mTagMode == TAG_MODE_SINGLE) { 145 | //如果已经是选中的就不需要操作 146 | if (tagFlowContainer.isChecked()) { 147 | return; 148 | } else { 149 | clearAllCheckedState();//清空所有view上的状态 150 | tagAdapter.removeAllChecked();//清空选中的集合 151 | tagAdapter.addChecked(tagAdapter.isCheckContent(tagAdapter.getItem(position), position)); 152 | } 153 | } 154 | //多选 155 | else if (mTagMode == TAG_MODE_MULTIPLE) { 156 | boolean isMutex = tagAdapter.getMutexCheck() != null && 157 | tagAdapter.isCheckContent(tagAdapter.getItem(position), position) == tagAdapter.getMutexCheck(); 158 | //之前是选中状态 159 | if (tagFlowContainer.isChecked()) { 160 | if (isMutex) return;//如果是互斥项而且是已经选中了直接return 161 | tagAdapter.removeChecked(tagAdapter.isCheckContent(tagAdapter.getItem(position), position)); 162 | if (tagAdapter.getCheckedList().size() == 0) { 163 | setMutexTagFlowView(true); 164 | } 165 | } 166 | //之前是未选中状态 167 | else { 168 | //如果是点击了互斥的 169 | if (isMutex) { 170 | tagAdapter.removeAllChecked();//清空选中的集合 171 | clearAllCheckedState();//清空所有view上的状态 172 | } 173 | //点击的不是互斥的 174 | else { 175 | setMutexTagFlowView(false); 176 | } 177 | tagAdapter.addChecked(tagAdapter.isCheckContent(tagAdapter.getItem(position), position)); 178 | } 179 | } 180 | //纯展示 181 | else { 182 | return; 183 | } 184 | tagFlowContainer.toggle(); 185 | if (onCheckChangeListener != null) 186 | onCheckChangeListener.onCheckChange(tagFlowContainer.isChecked(), position); 187 | 188 | } 189 | } 190 | 191 | 192 | /** 193 | * 单选模式 清空所有选中状态 194 | */ 195 | private void clearAllCheckedState() { 196 | if (mTagMode == TAG_MODE_SINGLE || mTagMode == TAG_MODE_MULTIPLE) { 197 | int count = getChildCount(); 198 | for (int i = 0; i < count; i++) { 199 | TagFlowContainer childAt = (TagFlowContainer) getChildAt(i); 200 | childAt.setChecked(false); 201 | } 202 | } 203 | } 204 | 205 | /** 206 | * 重置 207 | */ 208 | public void reset() { 209 | clearAllCheckedState(); 210 | if (tagAdapter != null) { 211 | tagAdapter.removeAllChecked(); 212 | } 213 | setMutexTagFlowView(true); 214 | } 215 | 216 | /** 217 | * 设置选中 218 | * 219 | * @param pos 220 | */ 221 | public void setChecked(Object... pos) { 222 | tagAdapter.addChecked(pos); 223 | } 224 | 225 | 226 | /** 227 | * 删除 228 | * 229 | * @param pos 230 | */ 231 | public void removeChecked(Object... pos) { 232 | tagAdapter.removeChecked(pos); 233 | } 234 | 235 | 236 | /** 237 | * 是否包含 238 | * 239 | * @param pos 240 | */ 241 | public boolean containsChecked(Object... pos) { 242 | return tagAdapter.containsChecked(pos); 243 | } 244 | 245 | 246 | /** 247 | * 后去所有选中的对象 按照对象数据顺序取 248 | * 249 | * @return 250 | */ 251 | public List getCheckedItems() { 252 | ArrayList items = new ArrayList(); 253 | for (int i = 0; i < tagAdapter.getCount(); i++) { 254 | if (tagAdapter.isChecked(tagAdapter.isCheckContent(tagAdapter.getItem(i), i))) { 255 | items.add(tagAdapter.getItem(i)); 256 | } 257 | } 258 | return items; 259 | } 260 | 261 | /** 262 | * 去掉设置的互斥数据 263 | * 264 | * @return 265 | */ 266 | public List getCheckedItemsFilter() { 267 | ArrayList items = new ArrayList(); 268 | for (int i = 0; i < tagAdapter.getCount(); i++) { 269 | if (tagAdapter.isCheckContent(tagAdapter.getItem(i), i) != tagAdapter.getMutexCheck() && 270 | tagAdapter.isChecked(tagAdapter.isCheckContent(tagAdapter.getItem(i), i))) { 271 | items.add(tagAdapter.getItem(i)); 272 | } 273 | } 274 | return items; 275 | } 276 | 277 | 278 | public TagFlowAdapter getAdapter() { 279 | return tagAdapter; 280 | } 281 | 282 | public Object getItem(int position) { 283 | return tagAdapter.getItem(position); 284 | } 285 | 286 | 287 | public void setOnCheckChangeListener(OnCheckChangeListener onCheckChangeListener) { 288 | this.onCheckChangeListener = onCheckChangeListener; 289 | } 290 | 291 | public void setOnTagClickListener(OnTagClickListener onTagClickListener) { 292 | this.onTagClickListener = onTagClickListener; 293 | } 294 | 295 | 296 | public interface OnCheckChangeListener { 297 | void onCheckChange(boolean isCheck, int position); 298 | } 299 | 300 | public interface OnTagClickListener { 301 | boolean onTagClick(View view, int position); 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /app/src/main/java/com/zwl/mybossdemo/mine/BossMineActivity.java: -------------------------------------------------------------------------------- 1 | package com.zwl.mybossdemo.mine; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | 5 | import android.os.Bundle; 6 | 7 | import com.zwl.mybossdemo.R; 8 | 9 | /** 10 | * Boss我的界面 11 | */ 12 | public class BossMineActivity extends AppCompatActivity { 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_boss_mine); 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/zwl/mybossdemo/mine/behavior/AppBarLayoutOverScrollViewBehavior.java: -------------------------------------------------------------------------------- 1 | package com.zwl.mybossdemo.mine.behavior; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | 8 | import androidx.coordinatorlayout.widget.CoordinatorLayout; 9 | 10 | import com.google.android.material.appbar.AppBarLayout; 11 | import com.zwl.mybossdemo.R; 12 | 13 | /** 14 | * 上滑时 15 | * 当AppBarLayout由展开到收起时,会依次调用onStartNestedScroll()->onNestedScrollAccepted()->onNestedPreScroll()->onStopNestedScroll() 16 | * 当AppBarLayout收起后继续向上滑动时,会依次调用onStartNestedScroll()->onNestedScrollAccepted()->onNestedPreScroll()->onNestedScroll()->onStopNestedScroll() 17 | *

18 | * 下滑时 19 | * 当AppBarLayout全部展开时(即未到顶部时),会依次调用onStartNestedScroll()->onNestedScrollAccepted()->onNestedPreScroll()->onNestedScroll()->onStopNestedScroll() 20 | * 当AppBarLayout全部展开时(即到顶部时),继续向下滑动屏幕,会依次调用onStartNestedScroll()->onNestedScrollAccepted()->onNestedPreScroll()->onNestedScroll()->onStopNestedScroll() 21 | *

22 | *

23 | * 当有快速滑动时会在onStopNestedScroll()前依次调用onNestedPreFling()->onNestedFling() 24 | * 所以要修改AppBarLayout的越界行为可以重写onNestedPreScroll()或onNestedScroll(),因为AppBarLayout收起时不会调用onNestedScroll(),所以只能选择重写onNestedPreScroll(),具体原因下面会有说明。 25 | */ 26 | public class AppBarLayoutOverScrollViewBehavior extends AppBarLayout.Behavior { 27 | private int mAppBarHeight; 28 | private View mCardView; 29 | private boolean isAnimate; 30 | private float mTotalDy; 31 | private float mLastScale; 32 | private int mLastBottom; 33 | private int mCardViewHeight; 34 | private int mLimitHeight; 35 | private View mToolBar; 36 | private float scaleValue = 2f / 3;// 显示卡片的三分之一 所以抛出三分之二 37 | private View mNameTitle; 38 | 39 | public AppBarLayoutOverScrollViewBehavior() { 40 | } 41 | 42 | public AppBarLayoutOverScrollViewBehavior(Context context, AttributeSet attrs) { 43 | super(context, attrs); 44 | } 45 | 46 | 47 | @Override 48 | public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl, int layoutDirection) { 49 | boolean handled = super.onLayoutChild(parent, abl, layoutDirection); 50 | if (null == mCardView) { 51 | mCardView = parent.findViewById(R.id.cardview); 52 | } 53 | if (null == mToolBar) { 54 | mToolBar = parent.findViewById(R.id.toolbar); 55 | } 56 | if (null == mNameTitle) { 57 | mNameTitle = parent.findViewById(R.id.name); 58 | } 59 | 60 | init(abl); 61 | return handled; 62 | } 63 | 64 | 65 | @Override 66 | public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY) { 67 | if (velocityY > 100) { 68 | isAnimate = false; 69 | } 70 | return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY); 71 | } 72 | 73 | 74 | @Override 75 | public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, View target, int type) { 76 | super.onStopNestedScroll(coordinatorLayout, abl, target, type); 77 | //恢复位置 78 | if (abl.getBottom() > mLimitHeight) { 79 | recovery(abl); 80 | } 81 | } 82 | 83 | 84 | @Override 85 | public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes, int type) { 86 | //开始滚动了 就动画归位 87 | isAnimate = true; 88 | return super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type); 89 | } 90 | 91 | 92 | @Override 93 | public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed, int type) { 94 | if (mCardView != null && ((dy <= 0 && child.getBottom() >= mLimitHeight) || (dy > 0 && child.getBottom() > mLimitHeight))) { 95 | scrollY(child, target, dy); 96 | } else { 97 | setViewAlpha(child, dy); 98 | super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type); 99 | } 100 | } 101 | 102 | 103 | /** 104 | * 初始化数据 105 | * 106 | * @param appBarLayout 107 | */ 108 | private void init(final AppBarLayout appBarLayout) { 109 | appBarLayout.setClipChildren(false); 110 | //整个AppbarLayout高度 111 | mAppBarHeight = appBarLayout.getMeasuredHeight(); 112 | //卡片的高度 113 | mCardViewHeight = mCardView.getMeasuredHeight(); 114 | //折叠正常的高度 115 | mLimitHeight = mAppBarHeight - (int) (mCardViewHeight * scaleValue); 116 | 117 | //默认1s折叠 118 | appBarLayout.postDelayed(new Runnable() { 119 | @Override 120 | public void run() { 121 | ValueAnimator anim = ValueAnimator.ofFloat(0, 1f).setDuration(200); 122 | anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 123 | @Override 124 | public void onAnimationUpdate(ValueAnimator animation) { 125 | float value = (float) animation.getAnimatedValue(); 126 | appBarLayout.setBottom((int) (mAppBarHeight - value * mCardViewHeight * scaleValue)); 127 | } 128 | }); 129 | anim.start(); 130 | } 131 | }, 1000); 132 | 133 | } 134 | 135 | 136 | /** 137 | * 混动 138 | * 139 | * @param child 140 | * @param target 141 | * @param dy 142 | */ 143 | private void scrollY(AppBarLayout child, View target, int dy) { 144 | mTotalDy += -dy; 145 | mTotalDy = Math.min(mTotalDy, mLimitHeight); 146 | mLastScale = Math.max(1f, 1f + (mTotalDy / mLimitHeight)); 147 | mLastBottom = mLimitHeight + (int) (mCardViewHeight * scaleValue * (mLastScale - 1)); 148 | child.setBottom(mLastBottom); 149 | target.setScrollY(0); 150 | } 151 | 152 | 153 | /** 154 | * 根据滑动设置 toolbar 名字显示效果 155 | * 156 | * @param target 157 | * @param dy 158 | */ 159 | private void setViewAlpha(View target, int dy) { 160 | float percent = Math.abs(target.getY() / mLimitHeight); 161 | if (percent >= 1) { 162 | percent = 1f; 163 | } 164 | //设置toolbar的透明度 165 | mToolBar.setAlpha(percent); 166 | 167 | //设置名字缩放 168 | mNameTitle.setScaleX(Math.max(0.8f, 1 - percent)); 169 | mNameTitle.setScaleY(Math.max(0.8f, 1 - percent)); 170 | 171 | //设置名字平移 172 | int offset = mNameTitle.getTop() - mToolBar.getTop(); 173 | mNameTitle.setTranslationY(-offset * percent); 174 | } 175 | 176 | /** 177 | * 恢复位置 178 | * 179 | * @param abl 180 | */ 181 | private void recovery(final AppBarLayout abl) { 182 | if (mTotalDy >= 0) { 183 | mTotalDy = 0; 184 | if (isAnimate) { 185 | ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1f).setDuration(200); 186 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 187 | @Override 188 | public void onAnimationUpdate(ValueAnimator animation) { 189 | float value = (float) animation.getAnimatedValue(); 190 | int offsetY = abl.getBottom() - mLimitHeight; 191 | abl.setBottom((int) (abl.getBottom() - (value * offsetY))); 192 | abl.setScrollY(0); 193 | } 194 | }); 195 | valueAnimator.start(); 196 | } else { 197 | abl.setBottom(mLimitHeight); 198 | abl.setScrollY(0); 199 | } 200 | } 201 | } 202 | 203 | 204 | } 205 | -------------------------------------------------------------------------------- /app/src/main/java/com/zwl/mybossdemo/note: -------------------------------------------------------------------------------- 1 | 设置显示时的高度问题 2 | 打开 BottomSheetDialog 的 setContentView() ->wrapInBottomSheet() 3 | 4 | 可以看到 BottomSheetDialog 中用到了 BottomSheetBehavior ,并且该 Behavior 是通过 5 | id 为 design_bottom_sheet 的 View 来获取的。也就是说我们只要获取到这个 View 就可以调用 6 | BottomSheetBehavior 的 setPeekHeight() 方法来设置 BottomSheetDialog 显示时的高度了。 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/boss_avatar_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yixiaolunhui/MyBossDemo/1d9fad07b0bc4e1d17b7e58dda16cbb6e509fb5f/app/src/main/res/drawable-xxhdpi/boss_avatar_7.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/boss_card_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yixiaolunhui/MyBossDemo/1d9fad07b0bc4e1d17b7e58dda16cbb6e509fb5f/app/src/main/res/drawable-xxhdpi/boss_card_bg.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/boss_company_detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yixiaolunhui/MyBossDemo/1d9fad07b0bc4e1d17b7e58dda16cbb6e509fb5f/app/src/main/res/drawable-xxhdpi/boss_company_detail.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/boss_company_detail_bottom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yixiaolunhui/MyBossDemo/1d9fad07b0bc4e1d17b7e58dda16cbb6e509fb5f/app/src/main/res/drawable-xxhdpi/boss_company_detail_bottom.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/boss_company_detail_bottom_top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yixiaolunhui/MyBossDemo/1d9fad07b0bc4e1d17b7e58dda16cbb6e509fb5f/app/src/main/res/drawable-xxhdpi/boss_company_detail_bottom_top.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/boss_filter_guanbi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yixiaolunhui/MyBossDemo/1d9fad07b0bc4e1d17b7e58dda16cbb6e509fb5f/app/src/main/res/drawable-xxhdpi/boss_filter_guanbi.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/boss_icon_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yixiaolunhui/MyBossDemo/1d9fad07b0bc4e1d17b7e58dda16cbb6e509fb5f/app/src/main/res/drawable-xxhdpi/boss_icon_down.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/boss_icon_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yixiaolunhui/MyBossDemo/1d9fad07b0bc4e1d17b7e58dda16cbb6e509fb5f/app/src/main/res/drawable-xxhdpi/boss_icon_up.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/boss_user_bottom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yixiaolunhui/MyBossDemo/1d9fad07b0bc4e1d17b7e58dda16cbb6e509fb5f/app/src/main/res/drawable-xxhdpi/boss_user_bottom.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/filter_btn2_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/filter_btn_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tag_bg_tag.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tag_textcolor.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_boss_mine.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 15 | 21 | 22 | 26 | 27 | 28 | 31 | 32 | 40 | 41 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 76 | 77 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_company_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 21 | 22 | 28 | 29 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 50 | 51 | 55 | 56 | 64 | 65 | 68 | 69 | 70 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 25 | 26 | 31 | 32 | 40 | 41 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 66 | 67 | 74 | 75 |