├── 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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
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 | 
11 | 
12 | 
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 |
43 |
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 |
6 |
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 |
--------------------------------------------------------------------------------