├── app ├── .gitignore ├── img │ ├── img01.jpg │ ├── img02.jpg │ └── img03.jpg ├── src │ └── main │ │ ├── res │ │ ├── drawable-hdpi │ │ │ ├── ic_msg.png │ │ │ ├── ic_scan.png │ │ │ ├── ic_calendar.png │ │ │ ├── ic_camera.png │ │ │ ├── ic_loading.png │ │ │ ├── ic_location.png │ │ │ ├── ic_search.png │ │ │ └── ic_shop_cart.png │ │ ├── drawable-xxhdpi │ │ │ ├── img1.jpg │ │ │ ├── img2.jpg │ │ │ ├── img3.jpg │ │ │ ├── ic_jd.png │ │ │ ├── img001.jpg │ │ │ ├── img002.jpg │ │ │ ├── img003.jpg │ │ │ ├── img004.jpg │ │ │ ├── img01.jpg │ │ │ ├── img02.jpg │ │ │ ├── img03.jpg │ │ │ ├── img04.jpg │ │ │ ├── img05.jpg │ │ │ └── header_bg.jpg │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── drawable │ │ │ ├── gray_circular_bg.xml │ │ │ ├── rounded_corners_white_bg.xml │ │ │ ├── rounded_corners_white_10_bg.xml │ │ │ └── ic_launcher_background.xml │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── styles.xml │ │ │ └── colors.xml │ │ └── layout │ │ │ ├── fragment_home.xml │ │ │ ├── banner_layout.xml │ │ │ ├── recommend_item_layout.xml │ │ │ ├── search_layout.xml │ │ │ ├── recyclerview_footer_text.xml │ │ │ ├── top_view_layout.xml │ │ │ ├── featured_layout.xml │ │ │ ├── frmagent_adapter_item.xml │ │ │ └── activity_main.xml │ │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── doublesucktopdemo │ │ │ ├── adapter │ │ │ ├── HomeViewPagerAdapter.java │ │ │ ├── RecommendAdapter.java │ │ │ ├── MyBannerAdapter.java │ │ │ └── FragmentAdapter.java │ │ │ ├── ProductBean.java │ │ │ ├── widget │ │ │ ├── RefreshHeaderView │ │ │ │ ├── PathWrapper.java │ │ │ │ ├── RectPathWrapper.java │ │ │ │ ├── TodayNewsHeader.java │ │ │ │ └── NewRefreshView.java │ │ │ ├── MyUnClassicRefreshFooter.java │ │ │ └── RecyclerViewDivider.java │ │ │ ├── Utils.java │ │ │ ├── HomeFragment.java │ │ │ └── MainActivity.java │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── vcs.xml ├── runConfigurations.xml ├── gradle.xml ├── misc.xml └── codeStyles │ └── Project.xml ├── .gitignore ├── README.md ├── gradle.properties ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | rootProject.name='DoubleSuckTopDemo' 3 | -------------------------------------------------------------------------------- /app/img/img01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/img/img01.jpg -------------------------------------------------------------------------------- /app/img/img02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/img/img02.jpg -------------------------------------------------------------------------------- /app/img/img03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/img/img03.jpg -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_msg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-hdpi/ic_msg.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-xxhdpi/img1.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-xxhdpi/img2.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-xxhdpi/img3.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-hdpi/ic_scan.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_jd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-xxhdpi/ic_jd.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img001.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-xxhdpi/img001.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img002.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-xxhdpi/img002.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img003.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-xxhdpi/img003.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img004.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-xxhdpi/img004.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-xxhdpi/img01.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-xxhdpi/img02.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-xxhdpi/img03.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-xxhdpi/img04.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/img05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-xxhdpi/img05.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-hdpi/ic_calendar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-hdpi/ic_camera.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-hdpi/ic_loading.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-hdpi/ic_location.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-hdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/header_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-xxhdpi/header_bg.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_shop_cart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/drawable-hdpi/ic_shop_cart.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weioule/DoubleSuckTopDemo/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/gray_circular_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_corners_white_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DoubleSuckTopDemo 3 | 4 | 5 | Hello blank fragment 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 26 10:01:20 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_corners_white_10_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | #FF1520 8 | #fff 9 | #666 10 | #2B2A29 11 | #f6f6f6 12 | 13 | #666 14 | 15 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea 38 | 39 | # Keystore files 40 | 41 | # MacOS 42 | .DS_Store 43 | 44 | *.json -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | -------------------------------------------------------------------------------- /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/main/res/layout/banner_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/doublesucktopdemo/adapter/HomeViewPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.doublesucktopdemo.adapter; 2 | 3 | import androidx.fragment.app.Fragment; 4 | import androidx.fragment.app.FragmentManager; 5 | import androidx.fragment.app.FragmentStatePagerAdapter; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created by weioule 11 | * on 2019/6/26. 12 | */ 13 | public class HomeViewPagerAdapter extends FragmentStatePagerAdapter { 14 | 15 | private List mlist; 16 | 17 | 18 | public HomeViewPagerAdapter(FragmentManager fm, List list) { 19 | super(fm); 20 | this.mlist = list; 21 | } 22 | 23 | @Override 24 | public Fragment getItem(int position) { 25 | return mlist.get(position); 26 | } 27 | 28 | @Override 29 | public int getCount() { 30 | return mlist.size(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DoubleSuckTopDemo 2 | 仿京东首页双层吸顶效果demo 3 | 4 | 今天已经是端午节假日最后一天了,快乐的时光总是很短暂的.

5 | 端午节期间用叮咚买菜App买菜,发现他们的App首页有两个吸顶功能,而下面的tabLayout那个吸顶,居然把上部分和下部分的手指滑动事件给阻断了,没能将滑动事件传递给到另一部分接着滑动.

6 | 叮咚买菜应该用户量不少,但对于我个人来说这用户体验还是不大能接受,用着感觉不太爽,可能是大厂的APP太溜了的原因吧,像京东这些也有双层的吸顶,但就不会有这种问题.

7 | 这放假疫情期间也不太敢出去,就打算来研究研究这个双层的吸顶效果. 8 | 9 | 所以仿着京东首页的效果,实现了双层吸顶的效果,并且不会阻断滑动事件的传递,下面看下效果图:
10 | ![image](https://github.com/weioule/DoubleSuckTopDemo/blob/master/app/img/img01.jpg)   11 | ![image](https://github.com/weioule/DoubleSuckTopDemo/blob/master/app/img/img02.jpg)   12 | ![image](https://github.com/weioule/DoubleSuckTopDemo/blob/master/app/img/img03.jpg)   13 | 14 | 思路解析: 15 | 16 | 第一个吸顶采用搜索框悬浮view固定显示隐藏实现,第二个吸顶是原生的CollapsingToolbarLayout + AppBarLayout实现,本来想着用两层CollapsingToolbarLayout + AppBarLayout嵌套,但经过验证后确实是行不通的. 17 | 18 | 第二个吸顶的位置得悬停在第一个吸顶的底部,所以用了Toolbar做占位,它的高度正是悬浮的搜索框的高度. 19 | 20 | 因为首页大多都会有刷新,所以还要考虑到与刷新的滑动冲突,这里设置了监听只有AppBarLayout是完全关闭状态时才可用. 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/doublesucktopdemo/adapter/RecommendAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.doublesucktopdemo.adapter; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import com.chad.library.adapter.base.BaseQuickAdapter; 7 | import com.chad.library.adapter.base.BaseViewHolder; 8 | import com.example.doublesucktopdemo.ProductBean; 9 | import com.example.doublesucktopdemo.R; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * Created by weioule 15 | * on 2019/6/26. 16 | */ 17 | public class RecommendAdapter extends BaseQuickAdapter { 18 | 19 | 20 | public RecommendAdapter(@Nullable List data) { 21 | super(R.layout.recommend_item_layout, data); 22 | } 23 | 24 | @Override 25 | protected void convert(@NonNull BaseViewHolder helper, ProductBean item) { 26 | helper.setImageResource(R.id.img, item.getImageResource()); 27 | helper.setText(R.id.name, item.getName()); 28 | helper.addOnClickListener(R.id.content); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/doublesucktopdemo/ProductBean.java: -------------------------------------------------------------------------------- 1 | package com.example.doublesucktopdemo; 2 | 3 | /** 4 | * Created by weioule 5 | * on 2019/6/26. 6 | */ 7 | public class ProductBean { 8 | 9 | private String name; 10 | private String price; 11 | private String content; 12 | private int imageResource; 13 | 14 | public String getName() { 15 | return name; 16 | } 17 | 18 | public void setName(String name) { 19 | this.name = name; 20 | } 21 | 22 | public String getPrice() { 23 | return price; 24 | } 25 | 26 | public void setPrice(String price) { 27 | this.price = price; 28 | } 29 | 30 | public String getContent() { 31 | return content; 32 | } 33 | 34 | public void setContent(String content) { 35 | this.content = content; 36 | } 37 | 38 | public int getImageResource() { 39 | return imageResource; 40 | } 41 | 42 | public void setImageResource(int imageResource) { 43 | this.imageResource = imageResource; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/recommend_item_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 18 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/doublesucktopdemo/widget/RefreshHeaderView/PathWrapper.java: -------------------------------------------------------------------------------- 1 | package com.example.doublesucktopdemo.widget.RefreshHeaderView; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Paint; 5 | import android.graphics.Path; 6 | import android.graphics.PathMeasure; 7 | 8 | /** 9 | * @author weioule 10 | * @date 2019/7/26. 11 | */ 12 | public class PathWrapper { 13 | protected Path mPath; //图形 Path 14 | protected float fraction; //绘制的比例 15 | 16 | public PathWrapper(Path path, float fraction) { 17 | mPath = path; 18 | this.fraction = fraction; 19 | } 20 | 21 | public void onDraw(Canvas canvas, Paint paint) { 22 | if (fraction <= 0) { 23 | return; 24 | } 25 | 26 | Path dst = new Path(); 27 | // 将 Path 与 PathMeasure 关联 28 | PathMeasure measure = new PathMeasure(mPath, false); 29 | 30 | float length = measure.getLength(); 31 | 32 | // 截取一部分 并使用 moveTo 保持截取得到的 Path 第一个点的位置不变 33 | measure.getSegment(0, length * fraction, dst, true); 34 | canvas.drawPath(dst, paint); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/doublesucktopdemo/widget/RefreshHeaderView/RectPathWrapper.java: -------------------------------------------------------------------------------- 1 | package com.example.doublesucktopdemo.widget.RefreshHeaderView; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Color; 5 | import android.graphics.Paint; 6 | import android.graphics.Path; 7 | import android.graphics.PathMeasure; 8 | 9 | /** 10 | * @author weioule 11 | * @date 2019/7/26. 12 | */ 13 | public class RectPathWrapper extends PathWrapper { 14 | 15 | Paint mPaint; 16 | 17 | public RectPathWrapper(Path path, float fraction) { 18 | super(path, fraction); 19 | mPaint = new Paint(); 20 | mPaint.setAntiAlias(true); 21 | mPaint.setStyle(Paint.Style.FILL); 22 | mPaint.setColor(Color.RED); 23 | } 24 | 25 | public void onDraw(Canvas canvas, Paint paint) { 26 | if (fraction <= 0) { 27 | return; 28 | } 29 | 30 | Path dst = new Path(); 31 | // 将 Path 与 PathMeasure 关联 32 | PathMeasure measure = new PathMeasure(mPath, false); 33 | 34 | float length = measure.getLength(); 35 | 36 | // 截取一部分 并使用 moveTo 保持截取得到的 Path 第一个点的位置不变 37 | measure.getSegment(0, length * fraction, dst, true); 38 | 39 | canvas.drawPath(dst, paint); 40 | 41 | canvas.drawPath(dst, mPaint); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Android 10 | 11 | 12 | 13 | 14 | Android 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Android 25 | 26 | 27 | LintAndroid 28 | 29 | 30 | 31 | 32 | Java 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/search_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 26 | 27 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/doublesucktopdemo/Utils.java: -------------------------------------------------------------------------------- 1 | package com.example.doublesucktopdemo; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.util.DisplayMetrics; 6 | 7 | /** 8 | * Created by weioule 9 | * on 2019/6/26. 10 | */ 11 | public class Utils { 12 | public static int dp2px(int dpValue) { 13 | final float scale = Resources.getSystem().getDisplayMetrics().density; 14 | return (int) (dpValue * scale + 0.5f); 15 | } 16 | 17 | 18 | /** 19 | * 计算屏幕宽度 getScreenWidth 20 | */ 21 | public static int getScreenWidth(Context context) { 22 | DisplayMetrics metric = context.getResources().getDisplayMetrics(); 23 | int width = metric.widthPixels; 24 | return width; 25 | } 26 | 27 | /** 28 | * 计算屏幕高度 getScreenWidth 29 | */ 30 | public static int getScreenHeight(Context context) { 31 | DisplayMetrics metric = context.getResources().getDisplayMetrics(); 32 | int height = metric.heightPixels; 33 | 34 | return height; 35 | } 36 | 37 | 38 | /** 39 | * 获取状态栏高度 40 | * 41 | * @param context context 42 | * @return 状态栏高度 43 | */ 44 | public static int getStatusBarHeight(Context context) { 45 | int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); 46 | return context.getResources().getDimensionPixelSize(resourceId); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/recyclerview_footer_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 18 | 19 | 25 | 26 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/doublesucktopdemo/adapter/MyBannerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.doublesucktopdemo.adapter; 2 | 3 | import android.view.ViewGroup; 4 | import android.widget.ImageView; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.recyclerview.widget.RecyclerView; 8 | 9 | import com.example.doublesucktopdemo.ProductBean; 10 | import com.youth.banner.adapter.BannerAdapter; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * Created by weioule 16 | * on 2019/6/26. 17 | */ 18 | public class MyBannerAdapter extends BannerAdapter { 19 | 20 | public MyBannerAdapter(List mDatas) { 21 | //设置数据,也可以调用banner提供的方法,或者自己在adapter中实现 22 | super(mDatas); 23 | } 24 | 25 | //创建ViewHolder,可以用viewType这个字段来区分不同的ViewHolder 26 | @Override 27 | public BannerViewHolder onCreateHolder(ViewGroup parent, int viewType) { 28 | ImageView imageView = new ImageView(parent.getContext()); 29 | //注意,必须设置为match_parent,这个是viewpager2强制要求的 30 | imageView.setLayoutParams(new ViewGroup.LayoutParams( 31 | ViewGroup.LayoutParams.MATCH_PARENT, 32 | ViewGroup.LayoutParams.MATCH_PARENT)); 33 | imageView.setScaleType(ImageView.ScaleType.FIT_XY); 34 | return new BannerViewHolder(imageView); 35 | } 36 | 37 | @Override 38 | public void onBindView(BannerViewHolder holder, ProductBean data, int position, int size) { 39 | holder.imageView.setImageResource(data.getImageResource()); 40 | } 41 | 42 | class BannerViewHolder extends RecyclerView.ViewHolder { 43 | ImageView imageView; 44 | 45 | public BannerViewHolder(@NonNull ImageView view) { 46 | super(view); 47 | this.imageView = view; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/res/layout/top_view_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 18 | 19 | 27 | 28 | 36 | 37 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/doublesucktopdemo/adapter/FragmentAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.doublesucktopdemo.adapter; 2 | 3 | import android.graphics.drawable.Drawable; 4 | import android.view.ViewGroup; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | 9 | import com.chad.library.adapter.base.BaseQuickAdapter; 10 | import com.chad.library.adapter.base.BaseViewHolder; 11 | import com.example.doublesucktopdemo.ProductBean; 12 | import com.example.doublesucktopdemo.R; 13 | import com.example.doublesucktopdemo.Utils; 14 | import com.makeramen.roundedimageview.RoundedImageView; 15 | 16 | import java.util.List; 17 | 18 | /** 19 | * Created by weioule 20 | * on 2019/6/26. 21 | */ 22 | public class FragmentAdapter extends BaseQuickAdapter { 23 | 24 | public FragmentAdapter(@Nullable List data) { 25 | super(R.layout.frmagent_adapter_item, data); 26 | } 27 | 28 | @Override 29 | protected void convert(@NonNull BaseViewHolder helper, ProductBean item) { 30 | helper.setImageResource(R.id.img, item.getImageResource()); 31 | helper.setText(R.id.tv_name, item.getName()); 32 | helper.setText(R.id.tv_price, item.getPrice()); 33 | helper.setText(R.id.tv_content, item.getContent()); 34 | helper.addOnClickListener(R.id.content_view); 35 | 36 | //30是spacing: 3x10 37 | int actual_width = (Utils.getScreenWidth(mContext) - Utils.dp2px(30)) / 2; 38 | Drawable drawable = mContext.getResources().getDrawable(item.getImageResource()); 39 | int image_width = drawable.getIntrinsicWidth(); 40 | int image_height = drawable.getIntrinsicHeight(); 41 | 42 | int iamgHeight = image_height * actual_width / image_width; 43 | 44 | RoundedImageView img = helper.getView(R.id.img); 45 | ViewGroup.LayoutParams params = img.getLayoutParams(); 46 | params.height = iamgHeight; 47 | img.setImageDrawable(drawable); 48 | 49 | helper.setVisible(R.id.tv_look_similar, helper.getAdapterPosition() % 3 == 0); 50 | helper.setVisible(R.id.iv_shop_cart, helper.getAdapterPosition() % 3 != 0); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | buildToolsVersion "29.0.2" 6 | defaultConfig { 7 | applicationId "com.example.doublesucktopdemo" 8 | minSdkVersion 26 9 | targetSdkVersion 29 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | debug { 16 | //LOG 和 LeakCanary 开关 17 | buildConfigField "boolean", "LOG_DEBUG", "true" 18 | buildConfigField "boolean", "USE_CANARY", "true" 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | release { 22 | buildConfigField "boolean", "LOG_DEBUG", "false" 23 | buildConfigField "boolean", "USE_CANARY", "false" 24 | minifyEnabled false 25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 26 | } 27 | } 28 | } 29 | 30 | dependencies { 31 | implementation fileTree(dir: 'libs', include: ['*.jar']) 32 | implementation 'androidx.appcompat:appcompat:1.0.2' 33 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 34 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 35 | testImplementation 'junit:junit:4.12' 36 | androidTestImplementation 'androidx.test.ext:junit:1.1.0' 37 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 38 | 39 | implementation 'com.google.android.material:material:1.0.0' 40 | implementation 'androidx.recyclerview:recyclerview:1.0.0' 41 | implementation 'com.flyco.tablayout:FlycoTabLayout_Lib:2.1.2@aar' 42 | 43 | implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.50' 44 | implementation 'com.github.yuruiyin:AppbarLayoutBehavior:v1.0.4' 45 | implementation 'com.youth.banner:banner:2.0.11' 46 | 47 | implementation 'com.makeramen:roundedimageview:2.3.0' 48 | implementation 'com.github.Ye-Miao:StatusBarUtil:1.7.5' 49 | 50 | implementation 'com.scwang.smart:refresh-layout-kernel:2.0.1' //核心必须依赖 51 | 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/res/layout/featured_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 22 | 23 | 32 | 33 | 34 | 41 | 42 | 50 | 51 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/doublesucktopdemo/HomeFragment.java: -------------------------------------------------------------------------------- 1 | package com.example.doublesucktopdemo; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.Toast; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.annotation.Nullable; 11 | import androidx.fragment.app.Fragment; 12 | import androidx.recyclerview.widget.RecyclerView; 13 | import androidx.recyclerview.widget.StaggeredGridLayoutManager; 14 | 15 | import com.chad.library.adapter.base.BaseQuickAdapter; 16 | import com.example.doublesucktopdemo.adapter.FragmentAdapter; 17 | 18 | import java.util.ArrayList; 19 | 20 | /** 21 | * Created by weioule 22 | * on 2019/6/26. 23 | */ 24 | public class HomeFragment extends Fragment { 25 | 26 | private RecyclerView mRecyclerView; 27 | private ArrayList list; 28 | 29 | public HomeFragment(ArrayList list) { 30 | this.list = list; 31 | } 32 | 33 | @Override 34 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 35 | return inflater.inflate(R.layout.fragment_home, container, false); 36 | } 37 | 38 | @Override 39 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 40 | super.onViewCreated(view, savedInstanceState); 41 | mRecyclerView = view.findViewById(R.id.recyclerView); 42 | FragmentAdapter adapter = new FragmentAdapter(list); 43 | 44 | View topPaddingView = new View(getContext()); 45 | topPaddingView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, Utils.dp2px(10))); 46 | adapter.addHeaderView(topPaddingView); 47 | 48 | final StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); 49 | mRecyclerView.setLayoutManager(layoutManager); 50 | mRecyclerView.setAdapter(adapter); 51 | adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() { 52 | @Override 53 | public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) { 54 | ProductBean productBean = (ProductBean) adapter.getItem(position); 55 | Toast.makeText(getContext(), productBean.getName(), Toast.LENGTH_SHORT).show(); 56 | } 57 | }); 58 | 59 | 60 | //下面的设置是防止第一次打开,当最后一行的子条目不是平齐的,导致最后一个子条目显示不全 61 | //防止item 交换位置 62 | layoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_NONE); 63 | mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 64 | @Override 65 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 66 | super.onScrollStateChanged(recyclerView, newState); 67 | //防止第一行到顶部有空白区域 68 | layoutManager.invalidateSpanAssignments(); 69 | } 70 | }); 71 | 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/res/layout/frmagent_adapter_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 20 | 21 | 26 | 27 | 34 | 35 | 46 | 47 | 56 | 57 | 68 | 69 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 16 | 17 | 20 | 21 | 27 | 28 | 32 | 33 | 37 | 38 | 43 | 44 | 49 | 50 | 51 | 52 | 53 | 57 | 58 | 59 | 69 | 70 | 71 | 76 | 77 | 78 | 79 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/doublesucktopdemo/widget/MyUnClassicRefreshFooter.java: -------------------------------------------------------------------------------- 1 | package com.example.doublesucktopdemo.widget; 2 | 3 | import android.animation.ObjectAnimator; 4 | import android.animation.ValueAnimator; 5 | import android.annotation.SuppressLint; 6 | import android.content.Context; 7 | import android.text.TextUtils; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.view.animation.LinearInterpolator; 12 | import android.widget.ImageView; 13 | import android.widget.LinearLayout; 14 | import android.widget.RelativeLayout; 15 | import android.widget.TextView; 16 | 17 | import androidx.annotation.Nullable; 18 | 19 | import com.example.doublesucktopdemo.R; 20 | import com.example.doublesucktopdemo.Utils; 21 | import com.scwang.smart.refresh.layout.api.RefreshFooter; 22 | import com.scwang.smart.refresh.layout.api.RefreshKernel; 23 | import com.scwang.smart.refresh.layout.api.RefreshLayout; 24 | import com.scwang.smart.refresh.layout.constant.RefreshState; 25 | import com.scwang.smart.refresh.layout.constant.SpinnerStyle; 26 | 27 | 28 | /** 29 | * Created by weioule 30 | * on 2019/4/9. 31 | */ 32 | @SuppressLint("RestrictedApi") 33 | public class MyUnClassicRefreshFooter extends LinearLayout implements RefreshFooter { 34 | 35 | private View rootView; 36 | private ImageView iv_loading; 37 | private TextView tv_footer; 38 | private String footerText; 39 | private ObjectAnimator objectAnimator; 40 | 41 | public MyUnClassicRefreshFooter(Context context) { 42 | this(context, null); 43 | } 44 | 45 | public MyUnClassicRefreshFooter(Context context, String text) { 46 | this(context, text, null); 47 | } 48 | 49 | public MyUnClassicRefreshFooter(Context context, String text, @Nullable AttributeSet attrs) { 50 | this(context, text, attrs, 0); 51 | } 52 | 53 | public MyUnClassicRefreshFooter(Context context, String text, @Nullable AttributeSet attrs, int defStyleAttr) { 54 | super(context, attrs, defStyleAttr); 55 | this.footerText = text; 56 | } 57 | 58 | @Override 59 | public View getView() { 60 | return this; 61 | } 62 | 63 | @Override 64 | public SpinnerStyle getSpinnerStyle() { 65 | return SpinnerStyle.Translate; 66 | } 67 | 68 | @Override 69 | protected void onAttachedToWindow() { 70 | super.onAttachedToWindow(); 71 | 72 | if (rootView == null) { 73 | rootView = View.inflate(getContext(), R.layout.recyclerview_footer_text, null); 74 | iv_loading = rootView.findViewById(R.id.footer_loading); 75 | tv_footer = rootView.findViewById(R.id.footer_text); 76 | if (!TextUtils.isEmpty(footerText)) 77 | tv_footer.setText(footerText); 78 | int size = Utils.dp2px(45); 79 | ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, size); 80 | rootView.setLayoutParams(params); 81 | addView(rootView); 82 | } 83 | } 84 | 85 | @SuppressLint("WrongConstant") 86 | @Override 87 | public void onStartAnimator(RefreshLayout layout, int height, int extendHeight) { 88 | objectAnimator = ObjectAnimator.ofFloat(iv_loading, "rotation", 0f, 360f); 89 | objectAnimator.setDuration(2500); 90 | objectAnimator.setInterpolator(new LinearInterpolator()); 91 | objectAnimator.setRepeatCount(10); 92 | objectAnimator.setRepeatMode(ValueAnimator.INFINITE); 93 | objectAnimator.start(); 94 | } 95 | 96 | @Override 97 | public int onFinish(RefreshLayout layout, boolean success) { 98 | if (null != objectAnimator && objectAnimator.isRunning()) { 99 | objectAnimator.end(); 100 | } 101 | return 0; 102 | } 103 | 104 | @Override 105 | public void setPrimaryColors(int... colors) { 106 | } 107 | 108 | @Override 109 | public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { 110 | } 111 | 112 | @Override 113 | public void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) { 114 | 115 | } 116 | 117 | @Override 118 | public void onReleased(RefreshLayout refreshLayout, int height, int extendHeight) { 119 | 120 | } 121 | 122 | @Override 123 | public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { 124 | } 125 | 126 | @Override 127 | public boolean isSupportHorizontalDrag() { 128 | return false; 129 | } 130 | 131 | @Override 132 | public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { 133 | 134 | } 135 | 136 | @Override 137 | public boolean setNoMoreData(boolean noMoreData) { 138 | return false; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /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/java/com/example/doublesucktopdemo/widget/RefreshHeaderView/TodayNewsHeader.java: -------------------------------------------------------------------------------- 1 | package com.example.doublesucktopdemo.widget.RefreshHeaderView; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.BitmapDrawable; 5 | import android.os.Handler; 6 | import android.os.Message; 7 | import android.util.AttributeSet; 8 | import android.util.TypedValue; 9 | import android.view.Gravity; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.LinearLayout; 13 | import android.widget.TextView; 14 | 15 | import androidx.annotation.ColorInt; 16 | import androidx.annotation.NonNull; 17 | import androidx.annotation.Nullable; 18 | 19 | import com.example.doublesucktopdemo.Utils; 20 | import com.scwang.smart.refresh.layout.api.RefreshHeader; 21 | import com.scwang.smart.refresh.layout.api.RefreshKernel; 22 | import com.scwang.smart.refresh.layout.api.RefreshLayout; 23 | import com.scwang.smart.refresh.layout.constant.RefreshState; 24 | import com.scwang.smart.refresh.layout.constant.SpinnerStyle; 25 | 26 | 27 | /** 28 | * @author weioule 29 | * @date 2019/7/26. 30 | */ 31 | public class TodayNewsHeader extends LinearLayout implements RefreshHeader { 32 | 33 | public static String REFRESH_HEADER_PULLDOWN = "下拉刷新"; 34 | public static String REFRESH_HEADER_RELEASE = "松开刷新"; 35 | public static String REFRESH_HEADER_REFRESHING = "更新中"; 36 | public static String REFRESH_HEADER_COMPLETE = "刷新完成"; 37 | private NewRefreshView mNewRefreshView; 38 | private TextView releaseText; 39 | private RefreshKernel mRefreshKernel; 40 | protected int mBackgroundColor; 41 | 42 | public TodayNewsHeader(Context context) { 43 | this(context, null); 44 | } 45 | 46 | public TodayNewsHeader(Context context, @Nullable AttributeSet attrs) { 47 | this(context, attrs, 0); 48 | } 49 | 50 | public TodayNewsHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 51 | super(context, attrs, defStyleAttr); 52 | initView(context); 53 | } 54 | 55 | private void initView(Context context) { 56 | this.setGravity(Gravity.CENTER_HORIZONTAL); 57 | this.setOrientation(LinearLayout.VERTICAL); 58 | 59 | mNewRefreshView = new NewRefreshView(context); 60 | LinearLayout.LayoutParams lpNewRefresh = new LayoutParams(Utils.dp2px(25), Utils.dp2px(25)); 61 | lpNewRefresh.setMargins(0, dip2px(context, 10), 0, 0); 62 | this.addView(mNewRefreshView, lpNewRefresh); 63 | 64 | LinearLayout.LayoutParams lpReleaseText = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 65 | lpReleaseText.setMargins(0, dip2px(context, 6), 0, dip2px(context, 5)); 66 | 67 | releaseText = new TextView(context); 68 | releaseText.setText(REFRESH_HEADER_PULLDOWN); 69 | releaseText.setTextColor(0xffffffff); 70 | releaseText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10); 71 | addView(releaseText, lpReleaseText); 72 | } 73 | 74 | private Handler mHandler = new Handler() { 75 | @Override 76 | public void handleMessage(Message msg) { 77 | mNewRefreshView.setDragState(); 78 | mHandler.sendEmptyMessageDelayed(0, 400); 79 | } 80 | }; 81 | 82 | @Override 83 | public void setPrimaryColors(@ColorInt int... colors) { 84 | if (colors.length > 0) { 85 | if (!(getBackground() instanceof BitmapDrawable)) { 86 | setPrimaryColor(colors[0]); 87 | } 88 | } 89 | } 90 | 91 | public void setPrimaryColor(@ColorInt int primaryColor) { 92 | setBackgroundColor(mBackgroundColor = primaryColor); 93 | if (mRefreshKernel != null) { 94 | mRefreshKernel.requestDrawBackgroundFor(this, mBackgroundColor); 95 | } 96 | } 97 | 98 | @Override 99 | public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { 100 | mRefreshKernel = kernel; 101 | mRefreshKernel.requestDrawBackgroundFor(this, mBackgroundColor); 102 | } 103 | 104 | @Override 105 | public void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) { 106 | mNewRefreshView.setFraction((percent - 0.8f) * 6f); 107 | } 108 | 109 | @NonNull 110 | @Override 111 | public View getView() { 112 | return this; 113 | } 114 | 115 | @NonNull 116 | @Override 117 | public SpinnerStyle getSpinnerStyle() { 118 | return SpinnerStyle.Translate; 119 | } 120 | 121 | @Override 122 | public void onReleased(RefreshLayout refreshLayout, int height, int extendHeight) { 123 | mHandler.removeCallbacksAndMessages(null); 124 | mHandler.sendEmptyMessage(0); 125 | } 126 | 127 | @Override 128 | public void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int extendHeight) { 129 | } 130 | 131 | @Override 132 | public int onFinish(@NonNull RefreshLayout refreshLayout, boolean success) { 133 | mHandler.removeCallbacksAndMessages(null); 134 | mNewRefreshView.setDrag(); 135 | return 0; 136 | } 137 | 138 | @Override 139 | public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { 140 | } 141 | 142 | @Override 143 | public boolean isSupportHorizontalDrag() { 144 | return false; 145 | } 146 | 147 | @Override 148 | public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { 149 | switch (newState) { 150 | case PullDownToRefresh: 151 | releaseText.setText(REFRESH_HEADER_PULLDOWN); 152 | break; 153 | case ReleaseToRefresh: 154 | releaseText.setText(REFRESH_HEADER_RELEASE); 155 | break; 156 | case Refreshing: 157 | releaseText.setText(REFRESH_HEADER_REFRESHING); 158 | break; 159 | case RefreshFinish: 160 | releaseText.setText(REFRESH_HEADER_COMPLETE); 161 | break; 162 | } 163 | } 164 | 165 | /** 166 | * 根据手机的分辨率从 dip 的单位 转成为 px(像素) 167 | */ 168 | public static int dip2px(Context context, float dpValue) { 169 | final float scale = context.getResources().getDisplayMetrics().density; 170 | return (int) (dpValue * scale + 0.5f); 171 | } 172 | 173 | @Override 174 | protected void onDetachedFromWindow() { 175 | super.onDetachedFromWindow(); 176 | mHandler.removeCallbacksAndMessages(null); 177 | mHandler = null; 178 | } 179 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/doublesucktopdemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.doublesucktopdemo; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.widget.LinearLayout; 6 | import android.widget.RelativeLayout; 7 | import android.widget.Toast; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.annotation.Nullable; 11 | import androidx.coordinatorlayout.widget.CoordinatorLayout; 12 | import androidx.fragment.app.Fragment; 13 | import androidx.fragment.app.FragmentActivity; 14 | import androidx.recyclerview.widget.GridLayoutManager; 15 | import androidx.recyclerview.widget.RecyclerView; 16 | import androidx.viewpager.widget.ViewPager; 17 | 18 | import com.chad.library.adapter.base.BaseQuickAdapter; 19 | import com.example.doublesucktopdemo.adapter.HomeViewPagerAdapter; 20 | import com.example.doublesucktopdemo.adapter.MyBannerAdapter; 21 | import com.example.doublesucktopdemo.adapter.RecommendAdapter; 22 | import com.example.doublesucktopdemo.widget.RecyclerViewDivider; 23 | import com.example.doublesucktopdemo.widget.RefreshHeaderView.TodayNewsHeader; 24 | import com.flyco.tablayout.SlidingTabLayout; 25 | import com.flyco.tablayout.listener.OnTabSelectListener; 26 | import com.google.android.material.appbar.AppBarLayout; 27 | import com.leaf.library.StatusBarUtil; 28 | import com.scwang.smart.refresh.layout.SmartRefreshLayout; 29 | import com.scwang.smart.refresh.layout.api.RefreshLayout; 30 | import com.scwang.smart.refresh.layout.listener.OnRefreshListener; 31 | import com.youth.banner.Banner; 32 | import com.youth.banner.indicator.CircleIndicator; 33 | 34 | import java.util.ArrayList; 35 | import java.util.List; 36 | 37 | 38 | /** 39 | * Created by weioule 40 | * on 2019/6/26. 41 | */ 42 | public class MainActivity extends FragmentActivity { 43 | private SmartRefreshLayout refreshView; 44 | private AppBarLayout mAppBarLayout; 45 | private RelativeLayout mFloatSearchRl; 46 | private LinearLayout mHeaderLl; 47 | private SlidingTabLayout mTabView; 48 | private ViewPager mViewPager; 49 | 50 | private int totalScrollRange, oldVerticalOffset = -1; 51 | private HomeViewPagerAdapter fragmentAdapter; 52 | private String[] titles = {"首页", "手机", "食品", "生鲜", "休闲零食", "家居厨具", "美妆"}; 53 | private ArrayList list = new ArrayList<>(); 54 | private List fragmentList; 55 | 56 | 57 | @Override 58 | protected void onCreate(@Nullable Bundle savedInstanceState) { 59 | super.onCreate(savedInstanceState); 60 | setContentView(R.layout.activity_main); 61 | initView(); 62 | addListener(); 63 | } 64 | 65 | private void initView() { 66 | StatusBarUtil.setColor(this, getColor(R.color.theme_color)); 67 | refreshView = findViewById(R.id.refresh_view); 68 | mFloatSearchRl = findViewById(R.id.rl_float_search); 69 | mAppBarLayout = findViewById(R.id.app_bar); 70 | mTabView = findViewById(R.id.tab_layout); 71 | mViewPager = findViewById(R.id.view_pager); 72 | mHeaderLl = findViewById(R.id.ll_header); 73 | refreshView.setRefreshHeader(new TodayNewsHeader(this)); 74 | addProductBeans(); 75 | getData(); 76 | } 77 | 78 | private void addProductBeans() { 79 | for (int i = 0; i < 10; i++) { 80 | ProductBean bean = new ProductBean(); 81 | switch (i) { 82 | case 0: 83 | case 9: 84 | bean.setName("鲜肉粽"); 85 | bean.setImageResource(R.drawable.img01); 86 | bean.setContent("当吃到蛋黄与鲜肉后,才知咸香入口的双重体验,能叫人如此回味"); 87 | break; 88 | case 1: 89 | case 8: 90 | bean.setName("豆沙粽"); 91 | bean.setImageResource(R.drawable.img02); 92 | bean.setContent("绵软细腻的豆沙,融化在温热糯米中,此时的豆沙馅儿入口即化,甜而不腻"); 93 | break; 94 | case 2: 95 | case 7: 96 | bean.setName("蜜枣粽"); 97 | bean.setImageResource(R.drawable.img03); 98 | bean.setContent("细细品味,蜜枣中渗出的糖蜜,浸润了周围的糯米,清甜绵密,糯而爽口"); 99 | break; 100 | case 3: 101 | case 6: 102 | bean.setName("栗蓉粽"); 103 | bean.setImageResource(R.drawable.img04); 104 | bean.setContent("清甜口味,料多馅足,满足你的挑剔味蕾"); 105 | break; 106 | case 4: 107 | case 5: 108 | bean.setName("咸鸭蛋"); 109 | bean.setImageResource(R.drawable.img05); 110 | bean.setContent("松沙酥软,入口绵密的咸鸭蛋,香醇浓郁的口感,让人不禁留恋忘返,回味不止"); 111 | break; 112 | } 113 | 114 | bean.setPrice("¥49.50"); 115 | list.add(bean); 116 | } 117 | } 118 | 119 | private void addListener() { 120 | mAppBarLayout.addOnOffsetChangedListener(offsetChangedListener); 121 | refreshView.setOnRefreshListener(new OnRefreshListener() { 122 | @Override 123 | public void onRefresh(@NonNull RefreshLayout refreshLayout) { 124 | loadData(); 125 | } 126 | }); 127 | } 128 | 129 | private void loadData() { 130 | refreshView.postDelayed(new Runnable() { 131 | @Override 132 | public void run() { 133 | refreshView.finishRefresh(); 134 | refreshView.setEnabled(true); 135 | } 136 | }, 800); 137 | } 138 | 139 | // 这里的处理是当AppBarLayout处于最顶部也就是完全打开状态verticalOffse=0时才允许刷新控件可用. 140 | private AppBarLayout.OnOffsetChangedListener offsetChangedListener = new AppBarLayout.OnOffsetChangedListener() { 141 | 142 | @Override 143 | public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffse) { 144 | totalScrollRange = appBarLayout.getTotalScrollRange(); 145 | 146 | if (oldVerticalOffset == verticalOffse) return; 147 | 148 | if (verticalOffse == 0) { 149 | refreshView.setEnabled(true); 150 | } else { 151 | refreshView.setEnabled(false); 152 | } 153 | 154 | if (verticalOffse <= -Utils.dp2px(40)) { 155 | mFloatSearchRl.setVisibility(View.VISIBLE); 156 | } else { 157 | mFloatSearchRl.setVisibility(View.GONE); 158 | } 159 | 160 | if (Math.abs(verticalOffse) >= totalScrollRange && totalScrollRange != 0) { 161 | mTabView.setBackgroundColor(getResources().getColor(R.color.white_color)); 162 | } else { 163 | mTabView.setBackgroundColor(getResources().getColor(R.color.background_color)); 164 | } 165 | 166 | oldVerticalOffset = verticalOffse; 167 | } 168 | }; 169 | 170 | public void getData() { 171 | mHeaderLl.removeAllViews(); 172 | 173 | addTopView(); 174 | addBannerView(); 175 | addRecommendView(); 176 | addFeaturedView(); 177 | initViewpager(); 178 | } 179 | 180 | private void addTopView() { 181 | View mTopView = getLayoutInflater().inflate(R.layout.top_view_layout, mHeaderLl, false); 182 | mHeaderLl.addView(mTopView); 183 | } 184 | 185 | private void addBannerView() { 186 | View mBannerView = getLayoutInflater().inflate(R.layout.banner_layout, mHeaderLl, false); 187 | Banner mBanner = mBannerView.findViewById(R.id.banner); 188 | 189 | ArrayList list = new ArrayList<>(); 190 | for (int i = 0; i < 3; i++) { 191 | ProductBean bean = new ProductBean(); 192 | bean.setName("banner " + i); 193 | switch (i) { 194 | case 0: 195 | bean.setImageResource(R.drawable.img1); 196 | break; 197 | case 1: 198 | bean.setImageResource(R.drawable.img2); 199 | break; 200 | case 2: 201 | bean.setImageResource(R.drawable.img3); 202 | break; 203 | } 204 | 205 | list.add(bean); 206 | } 207 | 208 | mBanner.addBannerLifecycleObserver(this) 209 | .setAdapter(new MyBannerAdapter(list)) 210 | .setIndicator(new CircleIndicator(this)) 211 | .start(); 212 | 213 | mHeaderLl.addView(mBannerView); 214 | } 215 | 216 | private void addRecommendView() { 217 | RecyclerView mRecyclerView = new RecyclerView(this); 218 | 219 | GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 5); 220 | mRecyclerView.setLayoutManager(gridLayoutManager); 221 | 222 | RecommendAdapter adapter = new RecommendAdapter(list); 223 | adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() { 224 | @Override 225 | public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) { 226 | ProductBean productBean = (ProductBean) adapter.getItem(position); 227 | Toast.makeText(MainActivity.this, productBean.getName(), Toast.LENGTH_SHORT).show(); 228 | } 229 | }); 230 | 231 | mRecyclerView.setAdapter(adapter); 232 | RecyclerViewDivider divider = new RecyclerViewDivider.Builder(this) 233 | .setStyle(RecyclerViewDivider.Style.BOTH) 234 | .setColor(0x00000000) 235 | .setOrientation(RecyclerViewDivider.GRIDE_VIW) 236 | .setSize(5) 237 | .build(); 238 | mRecyclerView.addItemDecoration(divider); 239 | 240 | mHeaderLl.addView(mRecyclerView); 241 | } 242 | 243 | private void addFeaturedView() { 244 | View view = getLayoutInflater().inflate(R.layout.featured_layout, mHeaderLl, false); 245 | mHeaderLl.addView(view); 246 | } 247 | 248 | private void initViewpager() { 249 | if (fragmentList == null) { 250 | fragmentList = new ArrayList<>(); 251 | } else { 252 | fragmentList.clear(); 253 | } 254 | 255 | for (int i = 0; i < titles.length; i++) { 256 | fragmentList.add(new HomeFragment(list)); 257 | } 258 | 259 | fragmentAdapter = new HomeViewPagerAdapter(getSupportFragmentManager(), fragmentList); 260 | mViewPager.setAdapter(fragmentAdapter); 261 | mTabView.setOnTabSelectListener(onTabSelectListener); 262 | mViewPager.addOnPageChangeListener(onPageChangeListener); 263 | 264 | mTabView.setViewPager(mViewPager, titles); 265 | } 266 | 267 | private OnTabSelectListener onTabSelectListener = new OnTabSelectListener() { 268 | @Override 269 | public void onTabSelect(int position) { 270 | mTabView.setCurrentTab(position); 271 | } 272 | 273 | @Override 274 | public void onTabReselect(int position) { 275 | } 276 | }; 277 | 278 | private ViewPager.OnPageChangeListener onPageChangeListener = new ViewPager.OnPageChangeListener() { 279 | @Override 280 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 281 | } 282 | 283 | @Override 284 | public void onPageSelected(int position) { 285 | mTabView.setCurrentTab(position); 286 | } 287 | 288 | @Override 289 | public void onPageScrollStateChanged(int state) { 290 | } 291 | }; 292 | 293 | /** 294 | * 控制appbar的滑动 295 | * 296 | * @param isScroll true 允许滑动 false 禁止滑动 297 | */ 298 | private void banAppBarScroll(boolean isScroll) { 299 | View mAppBarChildAt = mAppBarLayout.getChildAt(0); 300 | AppBarLayout.LayoutParams mAppBarParams = (AppBarLayout.LayoutParams) mAppBarChildAt.getLayoutParams(); 301 | if (isScroll && mAppBarParams.getScrollFlags() == 0) { 302 | mAppBarParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED); 303 | mAppBarChildAt.setLayoutParams(mAppBarParams); 304 | } else if (mAppBarParams.getScrollFlags() != 0) 305 | mAppBarParams.setScrollFlags(0); 306 | } 307 | 308 | /** 309 | * 控制appbar的开关 310 | * 需要手动开关时调用 311 | * 312 | * @param open true 打开 false 关闭 313 | */ 314 | public void switchAppbarLayout(boolean open) { 315 | CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams()).getBehavior(); 316 | if (behavior instanceof AppBarLayout.Behavior) { 317 | AppBarLayout.Behavior appBarLayoutBehavior = (AppBarLayout.Behavior) behavior; 318 | int topAndBottomOffset = appBarLayoutBehavior.getTopAndBottomOffset(); 319 | if (open && topAndBottomOffset != 0) { 320 | //打开 321 | appBarLayoutBehavior.setTopAndBottomOffset(0); 322 | } else if (!open) { 323 | //关闭 324 | if (totalScrollRange == 0) totalScrollRange = mAppBarLayout.getHeight(); 325 | appBarLayoutBehavior.setTopAndBottomOffset(-totalScrollRange); 326 | } 327 | } 328 | } 329 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/doublesucktopdemo/widget/RefreshHeaderView/NewRefreshView.java: -------------------------------------------------------------------------------- 1 | package com.example.doublesucktopdemo.widget.RefreshHeaderView; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.util.AttributeSet; 10 | import android.view.View; 11 | 12 | import androidx.annotation.Nullable; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /** 18 | * @author weioule 19 | * @date 2019/7/26. 20 | * 很重要一点,绘制不能从 坐标0 开始 会有 stroke*1 的偏移量 21 | * 绘制时会进行 canvas.translate的操作,所以 绘制坐标时不需要进行 坐标偏移计算 22 | */ 23 | @SuppressLint("NewApi") 24 | public class NewRefreshView extends View { 25 | 26 | private Context mContext; 27 | 28 | private Paint mPaint; 29 | private State mDragState; 30 | 31 | private Path roundPath; //最外层 圆形Path 32 | 33 | private int strokeWidth; //线宽 34 | 35 | //绘制不能从 坐标0 开始 会有 stroke*1 的偏移量 36 | private int contentWidth, contentHeight; //内容宽度 内容高度 37 | 38 | private float roundCorner; //外层 圆角矩形 圆角半径 39 | private float lineWidth; // 线条宽度 40 | 41 | private float rectWidth; //小矩形宽度 42 | 43 | private float shortLineWidth; //短线宽度 44 | 45 | private float spaceRectLine; //小矩形距 断线距离 46 | 47 | public int getContentHeight() { 48 | return contentHeight; 49 | } 50 | 51 | public NewRefreshView(Context context) { 52 | this(context, null); 53 | } 54 | 55 | public NewRefreshView(Context context, @Nullable AttributeSet attrs) { 56 | this(context, attrs, 0); 57 | } 58 | 59 | public NewRefreshView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 60 | super(context, attrs, defStyleAttr); 61 | init(context); 62 | } 63 | 64 | private void init(Context context) { 65 | mContext = context; 66 | mPaint = new Paint(); 67 | mPaint.setAntiAlias(true); 68 | mPaint.setDither(true); 69 | mPaint.setStrokeCap(Paint.Cap.ROUND); 70 | mPaint.setStyle(Paint.Style.STROKE); 71 | mPaint.setColor(Color.WHITE); 72 | strokeWidth = 2; 73 | mPaint.setStrokeWidth(strokeWidth); 74 | mPaint.setStrokeJoin(Paint.Join.ROUND); 75 | this.setLayerType(View.LAYER_TYPE_SOFTWARE, null); 76 | } 77 | 78 | 79 | /** 80 | * 初始化 path 81 | */ 82 | private void initPath() { 83 | 84 | roundPath = new Path(); 85 | 86 | roundPath.moveTo(contentWidth, roundCorner); 87 | roundPath.arcTo(contentWidth - roundCorner * 2, 0, contentWidth, roundCorner * 2, 0, -90, false); 88 | roundPath.lineTo(roundCorner, 0); 89 | roundPath.arcTo(0, 0, roundCorner * 2, roundCorner * 2, -90, -90, false); 90 | roundPath.lineTo(0, contentHeight - roundCorner); 91 | roundPath.arcTo(0, contentHeight - roundCorner * 2, roundCorner * 2, contentHeight, -180, -90, false); 92 | roundPath.lineTo(contentWidth - roundCorner, contentHeight); 93 | roundPath.arcTo(contentWidth - roundCorner * 2, contentHeight - roundCorner * 2, contentWidth, contentHeight, -270, -90, false); 94 | roundPath.close(); 95 | 96 | mDragState = new DragState(); 97 | } 98 | 99 | 100 | /** 101 | * 根据 左上 坐标 创建 矩形 Path 102 | * 103 | * @param left 左坐标 104 | * @param top 上坐标 105 | * @return 106 | */ 107 | public Path provideRectPath(float left, float top) { 108 | Path path = new Path(); 109 | path.moveTo(left + rectWidth, top); 110 | path.lineTo(left, top); 111 | path.lineTo(left, top + roundCorner * 2f); 112 | path.lineTo(left + rectWidth, top + roundCorner * 2f); 113 | path.close(); 114 | return path; 115 | } 116 | 117 | 118 | /** 119 | * 根据线条 左上 坐标和线宽创建线条 Path 120 | * 121 | * @param left 左坐标 122 | * @param top 上坐标 123 | * @param lineWidth 线宽 124 | * @return 125 | */ 126 | public Path provideLinePath(float left, float top, float lineWidth) { 127 | Path path = new Path(); 128 | path.moveTo(left, top); 129 | path.lineTo(left + lineWidth, top); 130 | return path; 131 | } 132 | 133 | 134 | public void setDragState() { 135 | if (mDragState instanceof DragState) { 136 | mDragState = new RefreshState1(); 137 | } else if (mDragState instanceof RefreshState1) { 138 | mDragState = new RefreshState2(); 139 | } else if (mDragState instanceof RefreshState2) { 140 | mDragState = new RefreshState3(); 141 | } else if (mDragState instanceof RefreshState3) { 142 | mDragState = new RefreshState4(); 143 | } else if (mDragState instanceof RefreshState4) { 144 | mDragState = new RefreshState1(); 145 | } 146 | postInvalidate(); 147 | } 148 | 149 | public void setDrag() { 150 | mDragState = new DragState(); 151 | postInvalidate(); 152 | } 153 | 154 | @Override 155 | protected void onDraw(Canvas canvas) { 156 | canvas.restore(); 157 | canvas.translate(strokeWidth, strokeWidth); 158 | mDragState.onDraw(canvas, mPaint); 159 | canvas.save(); 160 | } 161 | 162 | public void setFraction(float fraction) { 163 | if (mDragState instanceof DragState) { 164 | DragState dragState = (DragState) mDragState; 165 | dragState.setFraction(fraction); 166 | postInvalidate(); 167 | } 168 | } 169 | 170 | @Override 171 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 172 | 173 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 174 | 175 | int width = MeasureSpec.getSize(widthMeasureSpec); 176 | 177 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 178 | 179 | int height = MeasureSpec.getSize(heightMeasureSpec); 180 | 181 | 182 | //设定最小值时,增加 stoke 的偏移保证 边界绘制完整 183 | int minWidth = dip2px(25) + strokeWidth * 2; 184 | 185 | int minHeight = dip2px(25) + strokeWidth * 2; 186 | 187 | //判断 测量模式 如果是 wrap_content 需要对 宽高进行限定 188 | //同时确定 高度 也对 最小值进行限定 189 | 190 | if (widthMode == MeasureSpec.AT_MOST) { 191 | width = minWidth; 192 | } else if (widthMode == MeasureSpec.EXACTLY && width < minWidth) { 193 | width = minWidth; 194 | } 195 | 196 | if (heightMode == MeasureSpec.AT_MOST) { 197 | height = minHeight; 198 | } else if (heightMode == MeasureSpec.EXACTLY && height < minHeight) { 199 | height = minHeight; 200 | } 201 | 202 | // 在确定宽高之后 对内容 宽高再次进行计算,留出 stroke 的偏移 203 | contentWidth = width - strokeWidth * 2; 204 | 205 | contentHeight = height - strokeWidth * 2; 206 | 207 | setMeasuredDimension(width, height); 208 | 209 | initNeedParamn(); 210 | 211 | initPath(); 212 | } 213 | 214 | /** 215 | * 初始化绘制所需要的参数 216 | */ 217 | private void initNeedParamn() { 218 | roundCorner = contentHeight / 7f; 219 | 220 | lineWidth = contentWidth - roundCorner * 2; 221 | 222 | rectWidth = lineWidth / 2f; 223 | 224 | //短线条宽度 225 | shortLineWidth = (lineWidth / 8f) * 3f; 226 | 227 | //矩形与线条之间间距 228 | spaceRectLine = (lineWidth / 8f) * 1f; 229 | } 230 | 231 | public int dip2px(float dpValue) { 232 | final float scale = mContext.getResources().getDisplayMetrics().density; 233 | return (int) (dpValue * scale + 0.5f); 234 | } 235 | 236 | class RefreshState4 extends State { 237 | 238 | @Override 239 | protected void initStatePath() { 240 | PathWrapper pathWrapper = new PathWrapper(roundPath, 1); 241 | mPathList.add(pathWrapper); 242 | 243 | 244 | Path rectPath = provideRectPath(roundCorner, roundCorner * 4f); 245 | pathWrapper = new RectPathWrapper(rectPath, 1); 246 | mPathList.add(pathWrapper); 247 | 248 | 249 | float shortLeft = roundCorner + rectWidth + spaceRectLine; 250 | Path shortLine1 = provideLinePath(shortLeft, roundCorner * 4f, shortLineWidth); 251 | pathWrapper = new RectPathWrapper(shortLine1, 1); 252 | mPathList.add(pathWrapper); 253 | 254 | 255 | Path shortLine2 = provideLinePath(shortLeft, roundCorner * 5f, shortLineWidth); 256 | pathWrapper = new PathWrapper(shortLine2, 1); 257 | mPathList.add(pathWrapper); 258 | 259 | 260 | Path shortLine3 = provideLinePath(shortLeft, roundCorner * 6f, shortLineWidth); 261 | pathWrapper = new PathWrapper(shortLine3, 1); 262 | mPathList.add(pathWrapper); 263 | 264 | 265 | Path longLine1 = provideLinePath(roundCorner, roundCorner * 1f, lineWidth); 266 | pathWrapper = new PathWrapper(longLine1, 1); 267 | mPathList.add(pathWrapper); 268 | 269 | 270 | Path longLine2 = provideLinePath(roundCorner, roundCorner * 2f, lineWidth); 271 | pathWrapper = new PathWrapper(longLine2, 1); 272 | mPathList.add(pathWrapper); 273 | 274 | 275 | Path longLine3 = provideLinePath(roundCorner, roundCorner * 3f, lineWidth); 276 | pathWrapper = new PathWrapper(longLine3, 1); 277 | mPathList.add(pathWrapper); 278 | } 279 | } 280 | 281 | class RefreshState3 extends State { 282 | 283 | @Override 284 | protected void initStatePath() { 285 | PathWrapper pathWrapper = new PathWrapper(roundPath, 1); 286 | mPathList.add(pathWrapper); 287 | 288 | 289 | Path rectPath = provideRectPath(contentWidth - roundCorner - rectWidth, roundCorner * 4f); 290 | pathWrapper = new RectPathWrapper(rectPath, 1); 291 | mPathList.add(pathWrapper); 292 | 293 | 294 | float shortLeft = roundCorner; 295 | Path shortLine1 = provideLinePath(shortLeft, roundCorner * 4f, shortLineWidth); 296 | pathWrapper = new RectPathWrapper(shortLine1, 1); 297 | mPathList.add(pathWrapper); 298 | 299 | 300 | Path shortLine2 = provideLinePath(shortLeft, roundCorner * 5f, shortLineWidth); 301 | pathWrapper = new PathWrapper(shortLine2, 1); 302 | mPathList.add(pathWrapper); 303 | 304 | 305 | Path shortLine3 = provideLinePath(shortLeft, roundCorner * 6f, shortLineWidth); 306 | pathWrapper = new PathWrapper(shortLine3, 1); 307 | mPathList.add(pathWrapper); 308 | 309 | 310 | Path longLine1 = provideLinePath(roundCorner, roundCorner * 1f, lineWidth); 311 | pathWrapper = new PathWrapper(longLine1, 1); 312 | mPathList.add(pathWrapper); 313 | 314 | 315 | Path longLine2 = provideLinePath(roundCorner, roundCorner * 2f, lineWidth); 316 | pathWrapper = new PathWrapper(longLine2, 1); 317 | mPathList.add(pathWrapper); 318 | 319 | 320 | Path longLine3 = provideLinePath(roundCorner, roundCorner * 3f, lineWidth); 321 | pathWrapper = new PathWrapper(longLine3, 1); 322 | mPathList.add(pathWrapper); 323 | } 324 | } 325 | 326 | class RefreshState2 extends State { 327 | 328 | @Override 329 | protected void initStatePath() { 330 | PathWrapper pathWrapper = new PathWrapper(roundPath, 1); 331 | mPathList.add(pathWrapper); 332 | 333 | 334 | Path rectPath = provideRectPath(contentWidth - roundCorner - rectWidth, roundCorner); 335 | pathWrapper = new RectPathWrapper(rectPath, 1); 336 | mPathList.add(pathWrapper); 337 | 338 | 339 | float shortLeft = roundCorner; 340 | Path shortLine1 = provideLinePath(shortLeft, roundCorner, shortLineWidth); 341 | pathWrapper = new RectPathWrapper(shortLine1, 1); 342 | mPathList.add(pathWrapper); 343 | 344 | 345 | Path shortLine2 = provideLinePath(shortLeft, roundCorner * 2f, shortLineWidth); 346 | pathWrapper = new PathWrapper(shortLine2, 1); 347 | mPathList.add(pathWrapper); 348 | 349 | 350 | Path shortLine3 = provideLinePath(shortLeft, roundCorner * 3f, shortLineWidth); 351 | pathWrapper = new PathWrapper(shortLine3, 1); 352 | mPathList.add(pathWrapper); 353 | 354 | 355 | Path longLine1 = provideLinePath(roundCorner, roundCorner * 4f, lineWidth); 356 | pathWrapper = new PathWrapper(longLine1, 1); 357 | mPathList.add(pathWrapper); 358 | 359 | 360 | Path longLine2 = provideLinePath(roundCorner, roundCorner * 5f, lineWidth); 361 | pathWrapper = new PathWrapper(longLine2, 1); 362 | mPathList.add(pathWrapper); 363 | 364 | 365 | Path longLine3 = provideLinePath(roundCorner, roundCorner * 6f, lineWidth); 366 | pathWrapper = new PathWrapper(longLine3, 1); 367 | mPathList.add(pathWrapper); 368 | } 369 | } 370 | 371 | class RefreshState1 extends State { 372 | 373 | @Override 374 | protected void initStatePath() { 375 | PathWrapper pathWrapper = new PathWrapper(roundPath, 1); 376 | mPathList.add(pathWrapper); 377 | 378 | 379 | Path rectPath = provideRectPath(roundCorner, roundCorner); 380 | pathWrapper = new RectPathWrapper(rectPath, 1); 381 | mPathList.add(pathWrapper); 382 | 383 | 384 | float shortLeft = roundCorner + rectWidth + spaceRectLine; 385 | Path shortLine1 = provideLinePath(shortLeft, roundCorner, shortLineWidth); 386 | pathWrapper = new RectPathWrapper(shortLine1, 1); 387 | mPathList.add(pathWrapper); 388 | 389 | 390 | Path shortLine2 = provideLinePath(shortLeft, roundCorner * 2f, shortLineWidth); 391 | pathWrapper = new PathWrapper(shortLine2, 1); 392 | mPathList.add(pathWrapper); 393 | 394 | 395 | Path shortLine3 = provideLinePath(shortLeft, roundCorner * 3f, shortLineWidth); 396 | pathWrapper = new PathWrapper(shortLine3, 1); 397 | mPathList.add(pathWrapper); 398 | 399 | 400 | Path longLine1 = provideLinePath(roundCorner, roundCorner * 4f, lineWidth); 401 | pathWrapper = new PathWrapper(longLine1, 1); 402 | mPathList.add(pathWrapper); 403 | 404 | 405 | Path longLine2 = provideLinePath(roundCorner, roundCorner * 5f, lineWidth); 406 | pathWrapper = new PathWrapper(longLine2, 1); 407 | mPathList.add(pathWrapper); 408 | 409 | 410 | Path longLine3 = provideLinePath(roundCorner, roundCorner * 6f, lineWidth); 411 | pathWrapper = new PathWrapper(longLine3, 1); 412 | mPathList.add(pathWrapper); 413 | } 414 | } 415 | 416 | class DragState extends State { 417 | 418 | private float fraction = 0f; 419 | 420 | public void setFraction(float fraction) { 421 | this.fraction = fraction; 422 | mPathList.clear(); 423 | initStatePath(); 424 | } 425 | 426 | @Override 427 | protected void initStatePath() { 428 | PathWrapper pathWrapper = new PathWrapper(roundPath, fraction); 429 | mPathList.add(pathWrapper); 430 | 431 | 432 | Path rectPath = provideRectPath(roundCorner, roundCorner); 433 | pathWrapper = new RectPathWrapper(rectPath, Math.min(1, 4 * fraction)); 434 | mPathList.add(pathWrapper); 435 | 436 | 437 | float shortLeft = roundCorner + rectWidth + spaceRectLine; 438 | Path shortLine1 = provideLinePath(shortLeft, roundCorner, shortLineWidth); 439 | pathWrapper = new PathWrapper(shortLine1, Math.min(1, 12.5f * (fraction - 0.25f))); 440 | mPathList.add(pathWrapper); 441 | 442 | 443 | Path shortLine2 = provideLinePath(shortLeft, roundCorner * 2f, shortLineWidth); 444 | pathWrapper = new PathWrapper(shortLine2, Math.min(1, 12.5f * (fraction - 0.33f))); 445 | mPathList.add(pathWrapper); 446 | 447 | 448 | Path shortLine3 = provideLinePath(shortLeft, roundCorner * 3f, shortLineWidth); 449 | pathWrapper = new PathWrapper(shortLine3, Math.min(1, 12.5f * (fraction - 0.41f))); 450 | mPathList.add(pathWrapper); 451 | 452 | 453 | Path longLine1 = provideLinePath(roundCorner, roundCorner * 4f, lineWidth); 454 | pathWrapper = new PathWrapper(longLine1, Math.min(1, 6.25f * (fraction - 0.5f))); 455 | mPathList.add(pathWrapper); 456 | 457 | 458 | Path longLine2 = provideLinePath(roundCorner, roundCorner * 5f, lineWidth); 459 | pathWrapper = new PathWrapper(longLine2, Math.min(1, 6.25f * (fraction - 0.66f))); 460 | mPathList.add(pathWrapper); 461 | 462 | 463 | Path longLine3 = provideLinePath(roundCorner, roundCorner * 6f, lineWidth); 464 | pathWrapper = new PathWrapper(longLine3, Math.min(1, 6.25f * (fraction - 0.82f))); 465 | mPathList.add(pathWrapper); 466 | } 467 | } 468 | 469 | /** 470 | * 绘制的状态 471 | */ 472 | public abstract class State { 473 | 474 | protected List mPathList; 475 | 476 | public State() { 477 | mPathList = new ArrayList<>(); 478 | initStatePath(); 479 | } 480 | 481 | protected abstract void initStatePath(); 482 | 483 | void onDraw(Canvas canvas, Paint paint) { 484 | for (PathWrapper path : mPathList) { 485 | path.onDraw(canvas, paint); 486 | } 487 | } 488 | } 489 | } 490 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/doublesucktopdemo/widget/RecyclerViewDivider.java: -------------------------------------------------------------------------------- 1 | package com.example.doublesucktopdemo.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Rect; 6 | import android.graphics.drawable.ColorDrawable; 7 | import android.graphics.drawable.Drawable; 8 | import android.os.Build; 9 | import android.util.TypedValue; 10 | import android.view.View; 11 | import android.widget.LinearLayout; 12 | 13 | import androidx.annotation.ColorInt; 14 | import androidx.annotation.ColorRes; 15 | import androidx.annotation.IntDef; 16 | import androidx.core.view.ViewCompat; 17 | import androidx.recyclerview.widget.GridLayoutManager; 18 | import androidx.recyclerview.widget.RecyclerView; 19 | 20 | import static android.util.TypedValue.applyDimension; 21 | 22 | 23 | /** 24 | * @author weioule 25 | * @date 2019/7/22. 26 | */ 27 | public class RecyclerViewDivider extends RecyclerView.ItemDecoration { 28 | 29 | public static final int HORIZONTAL = LinearLayout.HORIZONTAL; 30 | public static final int VERTICAL = LinearLayout.VERTICAL; 31 | public static final int GRIDE_VIW = 888; 32 | 33 | /** 34 | * The divider drawable 35 | */ 36 | private Drawable mDivider; 37 | 38 | private int mOrientation; 39 | 40 | private final Rect mBounds = new Rect(); 41 | 42 | private Builder mBuilder; 43 | 44 | private RecyclerViewDivider(Builder builder) { 45 | mBuilder = builder; 46 | setOrientation(); 47 | setDividerDrawable(); 48 | } 49 | 50 | /** 51 | * Set Divider Drawable 52 | */ 53 | private void setDividerDrawable() { 54 | Drawable drawable; 55 | if (mBuilder.mDrawable != null) { 56 | drawable = mBuilder.mDrawable; 57 | } else { 58 | drawable = new DividerDrawable(mBuilder.mColor); 59 | } 60 | mDivider = drawable; 61 | } 62 | 63 | /** 64 | * Set Divider Orientation 65 | */ 66 | public void setOrientation() { 67 | int orientation = mBuilder.mOrientation; 68 | if (orientation != HORIZONTAL && orientation != VERTICAL && orientation != GRIDE_VIW) { 69 | throw new IllegalArgumentException("Invalid orientation. It should be either HORIZONTAL or VERTICAL or GRIDE_VIW"); 70 | } 71 | mOrientation = orientation; 72 | } 73 | 74 | int spanCount = 0; 75 | 76 | @Override 77 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 78 | int position = parent.getChildAdapterPosition(view); 79 | int count = parent.getAdapter().getItemCount(); 80 | if (mOrientation == GRIDE_VIW) { 81 | int width = mDivider.getIntrinsicWidth(); 82 | int height = mDivider.getIntrinsicHeight(); 83 | if (spanCount <= 0) { 84 | RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); 85 | if (null != layoutManager && layoutManager instanceof GridLayoutManager) { 86 | spanCount = ((GridLayoutManager) layoutManager).getSpanCount(); 87 | } 88 | } 89 | 90 | if (mBuilder.mShowTopDivider) { 91 | if (position % spanCount == 0) { 92 | //第一列 93 | if (position == 0) 94 | //spanCount == 1 兼容列数为1的情况 95 | outRect.set(width, height, spanCount == 1 ? width : 0, height); 96 | else 97 | outRect.set(width, 0, spanCount == 1 ? width : 0, !needSkip(position, count) ? height : 0); 98 | } else if (position % spanCount == spanCount - 1) { 99 | //最后一列 100 | if (position < spanCount) 101 | outRect.set(width, height, width, height); 102 | else 103 | outRect.set(width, 0, width, !needSkip(position, count) ? height : 0); 104 | } else { 105 | //第一行 106 | if (position < spanCount) 107 | outRect.set(width, height, 0, height); 108 | else 109 | outRect.set(width, 0, 0, !needSkip(position, count) ? height : 0); 110 | } 111 | } else { 112 | if (position % spanCount == 0) { 113 | //第一列 114 | if (position == 0) 115 | outRect.set(spanCount == 1 ? width : 0, 0, width, !needSkip(position, count) ? height : 0); 116 | else { 117 | outRect.set(spanCount == 1 ? width : 0, 0, width, !needSkip(position, count) ? height : 0); 118 | } 119 | } else if (position % spanCount == spanCount - 1) { 120 | //最后一列 121 | if (position < spanCount) 122 | outRect.set(0, 0, 0, !needSkip(position, count) ? height : 0); 123 | else 124 | outRect.set(0, 0, 0, !needSkip(position, count) ? height : 0); 125 | 126 | } else { 127 | //第一行 128 | if (position < spanCount) 129 | outRect.set(spanCount == 1 ? width : 0, 0, width, !needSkip(position, count) ? height : 0); 130 | else 131 | outRect.set(spanCount == 1 ? width : 0, 0, width, !needSkip(position, count) ? height : 0); 132 | } 133 | } 134 | 135 | } else if (mOrientation == VERTICAL) { 136 | int height = mDivider.getIntrinsicHeight(); 137 | if (position == 0 && mBuilder.mShowTopDivider) { 138 | outRect.set(0, height, 0, height); 139 | } else if (!needSkip(position, count)) { 140 | outRect.set(0, 0, 0, height); 141 | } 142 | } else { 143 | int width = mDivider.getIntrinsicWidth(); 144 | if (position == 0 && mBuilder.mShowTopDivider) { 145 | outRect.set(width, 0, width, 0); 146 | } else if (!needSkip(position, count)) { 147 | outRect.set(0, 0, width, 0); 148 | } 149 | } 150 | } 151 | 152 | private boolean needSkip(int position, int count) { 153 | // mEndSkipCount:跳过的行数,不绘制分割线 这里计算出所有跳过的条目 = (mEndSkipCount-1) * spanCount + 最后一行的条目 154 | return position < mBuilder.mStartSkipCount || mOrientation == GRIDE_VIW && mBuilder.mEndSkipCount > 0 && 155 | position >= count - ((mBuilder.mEndSkipCount > 1 ? (mBuilder.mEndSkipCount - 1) * spanCount : 0) + 156 | (count % spanCount == 0 ? spanCount : count % spanCount)) || position >= count - mBuilder.mEndSkipCount; 157 | } 158 | 159 | @Override 160 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 161 | if (parent.getLayoutManager() == null) { 162 | return; 163 | } 164 | if (mOrientation == GRIDE_VIW) { 165 | drawVertical(c, parent); 166 | drawHorizontal(c, parent); 167 | } else if (mOrientation == VERTICAL) { 168 | drawVertical(c, parent); 169 | } else { 170 | drawHorizontal(c, parent); 171 | } 172 | } 173 | 174 | /** 175 | * Draw vertical list divider 176 | * 177 | * @param canvas canvas 178 | * @param parent RecyclerView 179 | */ 180 | private void drawVertical(Canvas canvas, RecyclerView parent) { 181 | canvas.save(); 182 | final int left; 183 | final int right; 184 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && parent.getClipToPadding()) { 185 | left = parent.getPaddingLeft() + mBuilder.mMarginLeft; 186 | right = parent.getWidth() - parent.getPaddingRight() - mBuilder.mMarginRight; 187 | canvas.clipRect(left, parent.getPaddingTop(), right, parent.getHeight() - parent.getPaddingBottom()); 188 | } else { 189 | left = mBuilder.mMarginLeft; 190 | right = parent.getWidth() - mBuilder.mMarginRight; 191 | } 192 | 193 | int childCount = parent.getChildCount(); 194 | int top; 195 | int bottom; 196 | int count = parent.getAdapter().getItemCount(); 197 | for (int i = 0; i < childCount; i++) { 198 | final View child = parent.getChildAt(i); 199 | int position = parent.getChildAdapterPosition(child); 200 | if (needSkip(position, count)) { 201 | continue; 202 | } 203 | parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds); 204 | bottom = mBounds.bottom + Math.round(ViewCompat.getTranslationY(child)); 205 | top = bottom - mDivider.getIntrinsicHeight(); 206 | mDivider.setBounds(left, top, right, bottom); 207 | mDivider.draw(canvas); 208 | } 209 | 210 | if (childCount > 0 && mBuilder.mShowTopDivider) { 211 | final View child = parent.getChildAt(0); 212 | parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds); 213 | top = mBounds.top + Math.round(ViewCompat.getTranslationY(child)); 214 | bottom = top + mDivider.getIntrinsicHeight(); 215 | mDivider.setBounds(left, top, right, bottom); 216 | mDivider.draw(canvas); 217 | } 218 | canvas.restore(); 219 | } 220 | 221 | /** 222 | * Draw horizontal list divider 223 | * 224 | * @param canvas canvas 225 | * @param parent RecyclerView 226 | */ 227 | private void drawHorizontal(Canvas canvas, RecyclerView parent) { 228 | canvas.save(); 229 | final int top; 230 | final int bottom; 231 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && parent.getClipToPadding()) { 232 | top = parent.getPaddingTop() + mBuilder.mMarginTop; 233 | bottom = parent.getHeight() - parent.getPaddingBottom() - mBuilder.mMarginBottom; 234 | canvas.clipRect(parent.getPaddingLeft(), top, parent.getWidth() - parent.getPaddingRight(), bottom); 235 | } else { 236 | top = mBuilder.mMarginTop; 237 | bottom = parent.getHeight() - mBuilder.mMarginBottom; 238 | } 239 | 240 | int childCount = parent.getChildCount(); 241 | int left; 242 | int right; 243 | int count = parent.getAdapter().getItemCount(); 244 | for (int i = 0; i < childCount; i++) { 245 | final View child = parent.getChildAt(i); 246 | int position = parent.getChildAdapterPosition(child); 247 | if (needSkip(position, count)) { 248 | continue; 249 | } 250 | parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds); 251 | right = mBounds.right + Math.round(ViewCompat.getTranslationX(child)); 252 | left = right - mDivider.getIntrinsicWidth(); 253 | mDivider.setBounds(left, top, right, bottom); 254 | mDivider.draw(canvas); 255 | } 256 | if (childCount > 0 && mBuilder.mShowTopDivider) { 257 | final View child = parent.getChildAt(0); 258 | parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds); 259 | left = mBounds.left + Math.round(ViewCompat.getTranslationX(child)); 260 | right = left + mDivider.getIntrinsicWidth(); 261 | mDivider.setBounds(left, top, right, bottom); 262 | mDivider.draw(canvas); 263 | } 264 | canvas.restore(); 265 | } 266 | 267 | /** 268 | * RecyclerView Divider Builder 269 | */ 270 | public static class Builder { 271 | private Context mContext; 272 | private Drawable mDrawable; 273 | private int mOrientation = VERTICAL; 274 | private int mSize = 1; 275 | private int mMarginLeft = 0; 276 | private int mMarginRight = 0; 277 | private int mMarginTop = 0; 278 | private int mMarginBottom = 0; 279 | private int mColor = 0xFFD1D1D1; 280 | private int mStartSkipCount = 0; 281 | private int mEndSkipCount = 0; 282 | private @Style 283 | int mStyle = Style.END; 284 | 285 | private boolean mShowTopDivider; 286 | 287 | public Builder(Context context) { 288 | mContext = context; 289 | } 290 | 291 | /** 292 | * Set divider drawable 293 | * 294 | * @param drawable Divider drawable 295 | */ 296 | public Builder setDrawable(Drawable drawable) { 297 | mDrawable = drawable; 298 | return this; 299 | } 300 | 301 | /** 302 | * Set divider drawable resource 303 | * 304 | * @param drawableRes Divider drawable resource 305 | */ 306 | public Builder setDrawableRes(int drawableRes) { 307 | mDrawable = mContext.getResources().getDrawable(drawableRes); 308 | return this; 309 | } 310 | 311 | /** 312 | * Set divider style 313 | * 314 | * @param style divider style 315 | */ 316 | public Builder setStyle(@Style int style) { 317 | mStyle = style; 318 | return this; 319 | } 320 | 321 | /** 322 | * Set divider orientation 323 | * 324 | * @param orientation divider orientation 325 | */ 326 | public Builder setOrientation(int orientation) { 327 | mOrientation = orientation; 328 | return this; 329 | } 330 | 331 | /** 332 | * Set divider size 333 | * 334 | * @param size divider size 335 | */ 336 | public Builder setSize(float size) { 337 | return setSize(TypedValue.COMPLEX_UNIT_DIP, size); 338 | } 339 | 340 | /** 341 | * Set divider height 342 | * 343 | * @param unit divider height unit 344 | * @param height divider height 345 | */ 346 | public Builder setSize(int unit, float height) { 347 | mSize = getSizeValue(unit, height); 348 | return this; 349 | } 350 | 351 | /** 352 | * Set divider margin left 353 | * 354 | * @param marginLeft margin left value 355 | */ 356 | public Builder setMarginLeft(float marginLeft) { 357 | return setMarginLeft(TypedValue.COMPLEX_UNIT_DIP, marginLeft); 358 | } 359 | 360 | /** 361 | * Set divider margin left 362 | * 363 | * @param unit margin left value unit 364 | * @param marginLeft margin left value 365 | */ 366 | public Builder setMarginLeft(int unit, float marginLeft) { 367 | mMarginLeft = getSizeValue(unit, marginLeft); 368 | return this; 369 | } 370 | 371 | /** 372 | * Set divider margin right 373 | * 374 | * @param marginRight margin right value 375 | */ 376 | public Builder setMarginRight(float marginRight) { 377 | return setMarginRight(TypedValue.COMPLEX_UNIT_DIP, marginRight); 378 | } 379 | 380 | /** 381 | * Set divider margin right 382 | * 383 | * @param unit margin right value unit 384 | * @param marginRight margin right value 385 | */ 386 | public Builder setMarginRight(int unit, float marginRight) { 387 | mMarginRight = getSizeValue(unit, marginRight); 388 | return this; 389 | } 390 | 391 | /** 392 | * Set divider margin top 393 | * 394 | * @param marginTop margin top value 395 | */ 396 | public Builder setMarginTop(int marginTop) { 397 | return setMarginTop(TypedValue.COMPLEX_UNIT_DIP, marginTop); 398 | } 399 | 400 | /** 401 | * Set divider margin right 402 | * 403 | * @param unit margin right value unit 404 | * @param marginTop margin top value 405 | */ 406 | public Builder setMarginTop(int unit, int marginTop) { 407 | mMarginTop = getSizeValue(unit, marginTop); 408 | return this; 409 | } 410 | 411 | /** 412 | * Set divider margin bottom 413 | * 414 | * @param marginBottom margin bottom value 415 | */ 416 | public Builder setMarginBottom(float marginBottom) { 417 | return setMarginBottom(TypedValue.COMPLEX_UNIT_DIP, marginBottom); 418 | } 419 | 420 | /** 421 | * Set divider margin bottom 422 | * 423 | * @param unit margin bottom value unit 424 | * @param marginBottom margin bottom value 425 | */ 426 | public Builder setMarginBottom(int unit, float marginBottom) { 427 | mMarginBottom = getSizeValue(unit, marginBottom); 428 | return this; 429 | } 430 | 431 | /** 432 | * Set divider color 433 | * 434 | * @param color divider color 435 | */ 436 | public Builder setColor(@ColorInt int color) { 437 | mColor = color; 438 | return this; 439 | } 440 | 441 | /** 442 | * Set divider color 443 | * 444 | * @param colorRes divider color resource 445 | */ 446 | public Builder setColorRes(@ColorRes int colorRes) { 447 | mColor = mContext.getResources().getColor(colorRes); 448 | return this; 449 | } 450 | 451 | /** 452 | * Set skip count from start 453 | * 454 | * @param startSkipCount count from start 455 | */ 456 | public Builder setStartSkipCount(int startSkipCount) { 457 | mStartSkipCount = startSkipCount; 458 | return this; 459 | } 460 | 461 | public int getmStartSkipCount() { 462 | return mStartSkipCount; 463 | } 464 | 465 | /** 466 | * Set skip count before end 467 | * 468 | * @param endSkipCount count before end 469 | */ 470 | public Builder setEndSkipCount(int endSkipCount) { 471 | mEndSkipCount = endSkipCount; 472 | return this; 473 | } 474 | 475 | private int getSizeValue(int unit, float size) { 476 | return (int) applyDimension(unit, size, mContext.getResources().getDisplayMetrics()); 477 | } 478 | 479 | public RecyclerViewDivider build() { 480 | switch (mStyle) { 481 | case Style.BETWEEN: 482 | mEndSkipCount++; 483 | break; 484 | case Style.BOTH: 485 | mStartSkipCount--; 486 | break; 487 | case Style.END: 488 | break; 489 | case Style.START: 490 | mEndSkipCount++; 491 | break; 492 | } 493 | mShowTopDivider = (mStyle == Style.BOTH && mStartSkipCount < 0) || mStyle == Style.START; 494 | return new RecyclerViewDivider(this); 495 | } 496 | } 497 | 498 | /** 499 | * DividerDrawable 500 | */ 501 | private class DividerDrawable extends ColorDrawable { 502 | DividerDrawable(int color) { 503 | super(color); 504 | } 505 | 506 | @Override 507 | public int getIntrinsicWidth() { 508 | return mBuilder.mSize; 509 | } 510 | 511 | @Override 512 | public int getIntrinsicHeight() { 513 | return mBuilder.mSize; 514 | } 515 | } 516 | 517 | /** 518 | * Divider Style 519 | * END 包尾 520 | * START 包前 521 | * BOTH 前后都包 522 | * BETWEEN 前后都不包 523 | */ 524 | @IntDef({Style.BOTH, Style.START, Style.END, Style.BETWEEN}) 525 | public @interface Style { 526 | int END = 0;//包尾 527 | int START = 1;//包前 528 | int BOTH = 2;//前后都包 529 | int BETWEEN = 3;//前后都不包 530 | } 531 | } 532 | --------------------------------------------------------------------------------