├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── dictionaries │ └── gavin.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── view │ │ └── gavin │ │ └── com │ │ └── flexibleview │ │ ├── CoordinatorLayoutActivity.java │ │ ├── MainActivity.java │ │ ├── RecyclerViewActivity.java │ │ ├── ScrollViewActivity.java │ │ └── adapter │ │ ├── AbRecyclerViewAdapter.java │ │ ├── BaseRecyclerViewAdapter.java │ │ ├── MoreItemHelper.java │ │ └── SimpleListAdapter.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── header_background.jpg │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_coordinator_layout.xml │ ├── activity_main.xml │ ├── activity_recycler_view.xml │ ├── activity_scroll_view.xml │ ├── header.xml │ ├── item_recycler_view.xml │ └── refresh_layout.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── dependencies.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── gavin │ │ └── view │ │ └── flexible │ │ ├── FlexibleLayout.java │ │ ├── IFlexible.java │ │ ├── callback │ │ ├── OnPullListener.java │ │ ├── OnReadyPullListener.java │ │ └── OnRefreshListener.java │ │ └── util │ │ └── PullAnimatorUtil.java │ └── res │ ├── drawable │ └── flexible_loading.png │ └── values │ └── strings.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gavin-ZYX/FlexibleLayout/2185b81f1d72f6940377711b5aa816a5642f2231/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/dictionaries/gavin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 35 | 36 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 85 | 95 | 96 | 97 | 98 | 99 | 100 | 102 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 可以下拉放大的Layout 2 | 3 | ![](https://upload-images.jianshu.io/upload_images/1638147-1d8b8b96141f3a71.gif?imageMogr2/auto-orient/strip) 4 | 5 | ## 支持 6 | - 下拉刷新 7 | - ScrollView 8 | - RecyclerView 9 | - CoordinatorLayout 10 | (其他Layout需要处理改Layout的onTouchEvent事件,否则可能无法使用) 11 | 12 | ## 使用 13 | 14 | **依赖** 15 | 16 | ```gradle 17 | compile 'com.gavin.view.flexible:library:1.0.2' 18 | ``` 19 | 20 | **xml**(ScrollView) 21 | 22 | ```xml 23 | 25 | 27 | 30 | 33 | 35 | 36 | 37 | 38 | ``` 39 | 40 | **Activity** 41 | 42 | 下拉放大 43 | 44 | ```java 45 | private ImageView mHeader; 46 | private ScrollView mScrollView; 47 | private FlexibleLayout mFlexibleLayout; 48 | ... 49 | mFlexibleLayout.setHeader(mHeader) 50 | .setReadyListener(new OnReadyPullListener() { 51 | @Override 52 | public boolean isReady() { 53 | //下拉放大的条件 54 | return mScrollView.getScrollY() == 0; 55 | } 56 | }); 57 | ``` 58 | 59 | 下拉放大 + 刷新 60 | 61 | ```java 62 | mFlexibleLayout.setHeader(mHeader) 63 | .setReadyListener(new OnReadyPullListener() { 64 | @Override 65 | public boolean isReady() { 66 | return mScrollView.getScrollY() == 0; 67 | } 68 | }) 69 | .setRefreshable(true) 70 | .setDefaultRefreshView(new OnRefreshListener() { 71 | @Override 72 | public void onRefreshing() { 73 | //刷新操作 74 | ... 75 | //刷新完成后需要调用onRefreshComplete()通知FlexibleLayout 76 | mFlexibleLayout.onRefreshComplete(); 77 | } 78 | }); 79 | ``` 80 | 81 | ## 支持的方法 82 | 83 | **配置** 84 | 85 | |方法 | 功能 | 默认 | 86 | | - | - | - | 87 | | setEnable(boolean isEnable) | 允许下拉放大 | true | 88 | | setHeader(View header) | 设置Header | null | 89 | | setMaxPullHeight(int height) | Header最大下拉高度 | header 高度 + 1/3屏幕宽度 | 90 | | setRefreshable(boolean isEnable) | 是否允许下拉刷新 | false | 91 | | setMaxRefreshPullHeight(int height) | 刷新View最大下拉高度 | 1/3屏幕宽度 | 92 | | setRefreshSize(int size) | 刷新View的尺寸(正方形)| 1/15屏幕宽度 | 93 | | isRefreshing() | 是否正在刷新| | 94 | 95 | 96 | **监听** 97 | 98 | |方法 | 功能 | 99 | | - | - | 100 | | setReadyListener(OnReadyPullListener listener) | 设置准备监听 | 101 | | setOnPullListener(OnPullListener onPullListener) | 下拉监听 | 102 | | setRefreshView(View refreshView, OnRefreshListener listener) | 设置下拉刷新View 以及监听 | 103 | | setDefaultRefreshView(OnRefreshListener listener) | 使用默认的下拉刷新View | 104 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | buildToolsVersion rootProject.ext.buildToolsVersion 6 | 7 | defaultConfig { 8 | minSdkVersion rootProject.ext.minSdkVersion 9 | targetSdkVersion rootProject.ext.targetSdkVersion 10 | versionCode rootProject.ext.versionCode 11 | versionName rootProject.ext.versionName 12 | 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile rootProject.ext.dependencies['appcompat-v7'] 24 | compile project(':library') 25 | compile rootProject.ext.dependencies['recyclerview-v7'] 26 | compile rootProject.ext.dependencies['design'] 27 | } 28 | -------------------------------------------------------------------------------- /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/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/view/gavin/com/flexibleview/CoordinatorLayoutActivity.java: -------------------------------------------------------------------------------- 1 | package view.gavin.com.flexibleview; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.View; 9 | 10 | import com.gavin.view.flexible.FlexibleLayout; 11 | import com.gavin.view.flexible.callback.OnReadyPullListener; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import view.gavin.com.flexibleview.adapter.SimpleListAdapter; 17 | 18 | /** 19 | * Created by gavin 20 | * date 2018/6/18 21 | */ 22 | public class CoordinatorLayoutActivity extends AppCompatActivity { 23 | @Override 24 | protected void onCreate(@Nullable Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_coordinator_layout); 27 | 28 | View header = findViewById(R.id.iv); 29 | RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv); 30 | 31 | List list = new ArrayList<>(); 32 | for (int i = 0; i < 20; i++) { 33 | list.add("Item : " + i); 34 | } 35 | SimpleListAdapter adapter = new SimpleListAdapter(this, list); 36 | final LinearLayoutManager manager = new LinearLayoutManager(this); 37 | recyclerView.setLayoutManager(manager); 38 | recyclerView.setAdapter(adapter); 39 | 40 | 41 | FlexibleLayout flexibleLayout = (FlexibleLayout) findViewById(R.id.fv); 42 | flexibleLayout.setReadyListener(new OnReadyPullListener() { 43 | @Override 44 | public boolean isReady() { 45 | return manager.findFirstCompletelyVisibleItemPosition() == 0; 46 | } 47 | }); 48 | flexibleLayout.setHeader(header); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/view/gavin/com/flexibleview/MainActivity.java: -------------------------------------------------------------------------------- 1 | package view.gavin.com.flexibleview; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.View; 8 | 9 | /** 10 | * Created by gavin 11 | * date 2018/6/12 12 | */ 13 | public class MainActivity extends AppCompatActivity { 14 | @Override 15 | public void onCreate(@Nullable Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_main); 18 | 19 | } 20 | 21 | public void toScrollView(View view) { 22 | Intent intent = new Intent(this, ScrollViewActivity.class); 23 | startActivity(intent); 24 | } 25 | 26 | public void toRecyclerView(View view) { 27 | Intent intent = new Intent(this, RecyclerViewActivity.class); 28 | startActivity(intent); 29 | } 30 | 31 | public void toCoordinatorLayout(View view) { 32 | Intent intent = new Intent(this, CoordinatorLayoutActivity.class); 33 | startActivity(intent); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/view/gavin/com/flexibleview/RecyclerViewActivity.java: -------------------------------------------------------------------------------- 1 | package view.gavin.com.flexibleview; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | 11 | import com.gavin.view.flexible.FlexibleLayout; 12 | import com.gavin.view.flexible.callback.OnReadyPullListener; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | import view.gavin.com.flexibleview.adapter.SimpleListAdapter; 18 | 19 | /** 20 | * Created by gavin 21 | * date 2018/6/18 22 | */ 23 | public class RecyclerViewActivity extends AppCompatActivity { 24 | 25 | @Override 26 | protected void onCreate(@Nullable Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_recycler_view); 29 | RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv); 30 | View header = LayoutInflater.from(this).inflate(R.layout.header, null); 31 | View headerImage = header.findViewById(R.id.iv); 32 | 33 | List list = new ArrayList<>(); 34 | for (int i = 0; i < 20; i++) { 35 | list.add("Item : " + i); 36 | } 37 | SimpleListAdapter adapter = new SimpleListAdapter(this, list); 38 | adapter.addHeaderView(header); 39 | final LinearLayoutManager manager = new LinearLayoutManager(this); 40 | recyclerView.setLayoutManager(manager); 41 | recyclerView.setAdapter(adapter); 42 | 43 | 44 | FlexibleLayout flexibleLayout = (FlexibleLayout) findViewById(R.id.fv); 45 | flexibleLayout.setReadyListener(new OnReadyPullListener() { 46 | @Override 47 | public boolean isReady() { 48 | return manager.findFirstCompletelyVisibleItemPosition() == 0; 49 | } 50 | }); 51 | flexibleLayout.setHeader(headerImage); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/view/gavin/com/flexibleview/ScrollViewActivity.java: -------------------------------------------------------------------------------- 1 | package view.gavin.com.flexibleview; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.widget.ScrollView; 9 | 10 | import com.gavin.view.flexible.FlexibleLayout; 11 | import com.gavin.view.flexible.callback.OnReadyPullListener; 12 | import com.gavin.view.flexible.callback.OnRefreshListener; 13 | 14 | /** 15 | * Created by gavin 16 | * date 2018/6/18 17 | */ 18 | public class ScrollViewActivity extends AppCompatActivity { 19 | 20 | private View mHeader; 21 | private ScrollView mScrollView; 22 | private FlexibleLayout mFlexibleLayout; 23 | private View mRefreshView; 24 | 25 | @Override 26 | public void onCreate(@Nullable Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_scroll_view); 29 | initView(); 30 | mFlexibleLayout.setHeader(mHeader) 31 | .setReadyListener(new OnReadyPullListener() { 32 | @Override 33 | public boolean isReady() { 34 | return mScrollView.getScrollY() == 0; 35 | } 36 | }) 37 | .setRefreshable(true) 38 | .setDefaultRefreshView(new OnRefreshListener() { 39 | @Override 40 | public void onRefreshing() { 41 | new Thread(new Runnable() { 42 | @Override 43 | public void run() { 44 | try { 45 | Thread.sleep(2000); 46 | } catch (InterruptedException e) { 47 | e.printStackTrace(); 48 | } 49 | //刷新完成后需要调用onRefreshComplete()通知FlexibleLayout 50 | runOnUiThread(new Runnable() { 51 | @Override 52 | public void run() { 53 | mFlexibleLayout.onRefreshComplete(); 54 | } 55 | }); 56 | } 57 | }).start(); 58 | } 59 | }); 60 | } 61 | 62 | private void initView() { 63 | mFlexibleLayout = (FlexibleLayout) findViewById(R.id.ffv); 64 | mHeader = findViewById(R.id.header); 65 | mScrollView = (ScrollView) findViewById(R.id.sv); 66 | mRefreshView = LayoutInflater.from(this).inflate(R.layout.refresh_layout, null); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/view/gavin/com/flexibleview/adapter/AbRecyclerViewAdapter.java: -------------------------------------------------------------------------------- 1 | package view.gavin.com.flexibleview.adapter; 2 | 3 | import android.support.v4.widget.SwipeRefreshLayout; 4 | import android.support.v7.widget.LinearLayoutManager; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.support.v7.widget.StaggeredGridLayoutManager; 7 | 8 | /** 9 | * Created by dazhuanjia_rx on 16/9/8. 10 | */ 11 | public abstract class AbRecyclerViewAdapter extends RecyclerView.Adapter { 12 | 13 | protected boolean isLoading = false; //是否在加载更多 14 | protected boolean mLoadMoreEnable = false; //加载更多功能是否打开 15 | private int lastVisibleItem; 16 | 17 | private void attachRecyclerView(RecyclerView recyclerView) { 18 | if (recyclerView.getLayoutManager() instanceof LinearLayoutManager) { 19 | final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 20 | recyclerView.addOnScrollListener( 21 | new RecyclerView.OnScrollListener() { 22 | @Override 23 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 24 | super.onScrolled(recyclerView, dx, dy); 25 | lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition(); 26 | } 27 | 28 | @Override 29 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 30 | super.onScrollStateChanged(recyclerView, newState); 31 | if (swipeRefreshLayout != null && swipeRefreshLayout.isRefreshing()) { //正在刷新时不加载更多 32 | return; 33 | } 34 | if (newState == RecyclerView.SCROLL_STATE_IDLE 35 | && lastVisibleItem + 1 == getItemCount() && !isLoading && mLoadMoreEnable && canLoadMore()) { 36 | isLoading = true; 37 | } 38 | } 39 | } 40 | ); 41 | } else if (recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) { 42 | final StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) recyclerView.getLayoutManager(); 43 | recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 44 | @Override 45 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 46 | super.onScrolled(recyclerView, dx, dy); 47 | int[] into = new int[staggeredGridLayoutManager.getSpanCount()]; 48 | (staggeredGridLayoutManager).findLastVisibleItemPositions(into); 49 | lastVisibleItem = findMax(into); 50 | 51 | } 52 | 53 | @Override 54 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 55 | super.onScrollStateChanged(recyclerView, newState); 56 | if (swipeRefreshLayout != null && swipeRefreshLayout.isRefreshing()) { //正在刷新时不加载更多 57 | return; 58 | } 59 | if (newState == RecyclerView.SCROLL_STATE_IDLE 60 | && lastVisibleItem + 1 == getItemCount() && !isLoading && mLoadMoreEnable && canLoadMore()) { 61 | isLoading = true; 62 | } 63 | } 64 | }); 65 | } 66 | } 67 | /** 68 | * 获取数组中的最大值 69 | * 70 | * @param lastPositions 需要找到最大值的数组 71 | * @return 数组中的最大值 72 | */ 73 | private int findMax(int[] lastPositions) { 74 | int max = lastPositions[0]; 75 | for (int value : lastPositions) { 76 | if (value > max) { 77 | max = value; 78 | } 79 | } 80 | return max; 81 | } 82 | 83 | protected boolean canLoadMore() { 84 | return true; 85 | } 86 | 87 | /** 88 | * 是否正在加载更多 89 | * 90 | * @return 91 | */ 92 | public boolean isLoadMoring() { 93 | return isLoading; 94 | } 95 | 96 | /** 97 | * @param mLoadMoreEnable 是否可以加载更多 98 | */ 99 | public void enableLoadMore(boolean mLoadMoreEnable) { 100 | this.mLoadMoreEnable = mLoadMoreEnable; 101 | } 102 | 103 | protected SwipeRefreshLayout swipeRefreshLayout; 104 | 105 | public void attachSwipeRefreshLayout(SwipeRefreshLayout swipeRefreshLayout) { 106 | this.swipeRefreshLayout = swipeRefreshLayout; 107 | } 108 | 109 | /** 110 | * 是否在刷新或加载更多 111 | * 112 | * @return 113 | */ 114 | public boolean isRefreshOrLoadMoring() { 115 | return isLoadMoring() || isRefreshing(); 116 | } 117 | 118 | /** 119 | * 是否在刷新 120 | * 121 | * @return 122 | */ 123 | public boolean isRefreshing() { 124 | return swipeRefreshLayout != null && swipeRefreshLayout.isRefreshing(); 125 | } 126 | 127 | /** 128 | * 刷新完成 129 | */ 130 | public void refreshComplete() { 131 | if (swipeRefreshLayout != null && swipeRefreshLayout.isRefreshing()) { 132 | swipeRefreshLayout.setRefreshing(false); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /app/src/main/java/view/gavin/com/flexibleview/adapter/BaseRecyclerViewAdapter.java: -------------------------------------------------------------------------------- 1 | package view.gavin.com.flexibleview.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.LayoutRes; 5 | import android.support.annotation.NonNull; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.LinearLayout; 11 | import android.widget.ProgressBar; 12 | import android.widget.TextView; 13 | 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 19 | import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 20 | 21 | /** 22 | * Created by dazhuanjia_rx on 16/8/31. 23 | */ 24 | public abstract class BaseRecyclerViewAdapter extends AbRecyclerViewAdapter { 25 | public Context context; 26 | public List list; 27 | 28 | private static final int HEADER_VIEW = 0x00000111; 29 | private static final int LOADING_VIEW = 0x00000222; 30 | private static final int FOOTER_VIEW = 0x00000333; 31 | private static final int ITEM_VIEW = 0x00000444; 32 | 33 | // 缓存对应 viewType 的 item 34 | private HashMap moreItemHelperSparseArray = new HashMap<>(); 35 | 36 | public BaseRecyclerViewAdapter(Context context, @NonNull List list) { 37 | this.context = context; 38 | this.list = list; 39 | } 40 | 41 | /** 42 | * 在构造方法里调用 43 | * 44 | * @param moreItemHelper 45 | */ 46 | protected void addMoreItemHelper(MoreItemHelper moreItemHelper) { 47 | moreItemHelperSparseArray.put(moreItemHelper.getItemType(), moreItemHelper); 48 | } 49 | 50 | @Override 51 | final public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 52 | View view; 53 | switch (viewType) { 54 | case HEADER_VIEW: 55 | return new HeadViewHolder(mHeaderLayout); 56 | case FOOTER_VIEW: 57 | return new FootViewHolder(mFooterLayout); 58 | default: 59 | if (moreItemHelperSparseArray.size() != 0) { 60 | MoreItemHelper moreItemHelper = moreItemHelperSparseArray.get(viewType); 61 | return moreItemHelper.createViewHolder(context, parent); 62 | } else { 63 | view = LayoutInflater.from(context).inflate(getLayoutId(), parent, false); 64 | return getViewHolder(view); 65 | } 66 | } 67 | } 68 | 69 | @LayoutRes 70 | protected abstract int getLayoutId(); 71 | 72 | @NonNull 73 | protected abstract RecyclerView.ViewHolder getViewHolder(View view); 74 | 75 | @Override 76 | final public int getItemCount() { 77 | int listSize = getItemSize(); 78 | return listSize + getHeaderLayoutCount() + getFooterLayoutCount() + getLoadMoreViewCount(); 79 | } 80 | 81 | /** 82 | * 返回item个数 除了head footer loadMoreView 83 | * 子类可重写 84 | * 85 | * @return 86 | */ 87 | public int getItemSize() { 88 | if (moreItemHelperSparseArray.size() != 0) { 89 | int count = 0; 90 | Set integers = moreItemHelperSparseArray.keySet(); 91 | for (Integer viewType : integers) { 92 | MoreItemHelper moreItemHelper = moreItemHelperSparseArray.get(viewType); 93 | count = count + moreItemHelper.getItemCount(); 94 | } 95 | return count; 96 | } 97 | return list.size(); 98 | } 99 | 100 | @Override 101 | public int getItemViewType(int position) { 102 | int numHeaders = getHeaderLayoutCount(); 103 | if (position < numHeaders) { 104 | return HEADER_VIEW; 105 | } else { 106 | int adjPosition = position - numHeaders; 107 | int adapterCount = getItemSize(); 108 | if (adjPosition < adapterCount) { 109 | if (moreItemHelperSparseArray.size() != 0) { 110 | return getMoreItemHelperViewType(adjPosition); 111 | } else { 112 | return ITEM_VIEW; 113 | } 114 | } else { 115 | adjPosition = adjPosition - adapterCount; 116 | int numFooters = getFooterLayoutCount(); 117 | if (adjPosition < numFooters) { 118 | return FOOTER_VIEW; 119 | } else { 120 | return LOADING_VIEW; 121 | } 122 | } 123 | } 124 | } 125 | 126 | /** 127 | * 返回对应 position 的viewType 128 | * 129 | * @param position 130 | * @return 131 | */ 132 | protected int getMoreItemHelperViewType(int position) { 133 | return 0; 134 | } 135 | 136 | @Override 137 | final public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 138 | int viewType = holder.getItemViewType(); 139 | switch (viewType) { 140 | case 0: 141 | onBindView(holder, holder.getLayoutPosition() - getHeaderLayoutCount()); 142 | break; 143 | case LOADING_VIEW: 144 | setLoadMoreView((LoadMoreViewHolder) holder); 145 | break; 146 | case HEADER_VIEW: 147 | break; 148 | case FOOTER_VIEW: 149 | break; 150 | default: 151 | if (moreItemHelperSparseArray.size() != 0) { 152 | moreItemHelperSparseArray.get(viewType).onBindView(holder, getRealPosition(viewType, holder.getLayoutPosition() - getHeaderLayoutCount())); 153 | } else { 154 | onBindView(holder, holder.getLayoutPosition() - getHeaderLayoutCount()); 155 | } 156 | break; 157 | } 158 | } 159 | 160 | /** 161 | * 多 item 布局需要重写该方法拿到对应的 position 162 | * 163 | * @param viewType 164 | * @param position 165 | * @return 166 | */ 167 | protected int getRealPosition(int viewType, int position) { 168 | return position; 169 | } 170 | 171 | protected abstract void onBindView(RecyclerView.ViewHolder holder, int position); 172 | 173 | /** 174 | * @param offset 175 | * @param limit 176 | * @param newList 177 | * @return true 代表有数据 false代表无数据 178 | */ 179 | public boolean updateList(int offset, int limit, List newList) { 180 | if (offset == 0) { 181 | list.clear(); 182 | notifyDataSetChanged(); 183 | resetLoadMoreStatus(); 184 | } 185 | if (newList != null && newList.size() != 0) { 186 | int size = list.size(); 187 | list.addAll(newList); 188 | notifyItemRangeInserted(size + getHeaderLayoutCount(), newList.size()); 189 | notifyItemRangeChanged(size + getHeaderLayoutCount(), newList.size()); 190 | if (newList.size() < limit) { 191 | loadMoreEnd(); 192 | } else { 193 | loadMoreComplete(); 194 | } 195 | return true; 196 | } else { //没有新数据关闭加载更多 197 | loadMoreEnd(); 198 | return list.size() != 0; 199 | } 200 | } 201 | 202 | /** 203 | * 移除item 204 | * 205 | * @param position 206 | * @return true: list empty ; false: list size more than 0 207 | */ 208 | public boolean removeItem(int position) { 209 | if (list.size() > position) { 210 | list.remove(position); 211 | if (list.size() == 0) { 212 | notifyDataSetChanged(); 213 | return true; 214 | } else { 215 | notifyItemRemoved(position); 216 | if (position != list.size()) { 217 | notifyItemRangeChanged(position, list.size() - position); 218 | } 219 | } 220 | } 221 | return false; 222 | } 223 | 224 | //header footer 225 | private LinearLayout mHeaderLayout; 226 | private LinearLayout mFooterLayout; 227 | 228 | /** 229 | * Return root layout of header 230 | */ 231 | public LinearLayout getHeaderLayout() { 232 | return mHeaderLayout; 233 | } 234 | 235 | /** 236 | * Return root layout of footer 237 | */ 238 | public LinearLayout getFooterLayout() { 239 | return mFooterLayout; 240 | } 241 | 242 | /** 243 | * Append header to the rear of the mHeaderLayout. 244 | * 245 | * @param header 246 | */ 247 | public int addHeaderView(View header) { 248 | return addHeaderView(header, -1); 249 | } 250 | 251 | /** 252 | * Add header view to mHeaderLayout and set header view position in mHeaderLayout. 253 | * When index = -1 or index >= child count in mHeaderLayout, 254 | * the effect of this method is the same as that of {@link #addHeaderView(View)}. 255 | * 256 | * @param header 257 | * @param index the position in mHeaderLayout of this header. 258 | * When index = -1 or index >= child count in mHeaderLayout, 259 | * the effect of this method is the same as that of {@link #addHeaderView(View)}. 260 | */ 261 | public int addHeaderView(View header, int index) { 262 | return addHeaderView(header, index, LinearLayout.VERTICAL); 263 | } 264 | 265 | /** 266 | * @param header 267 | * @param index 268 | * @param orientation 269 | */ 270 | public int addHeaderView(View header, int index, int orientation) { 271 | if (mHeaderLayout == null) { 272 | mHeaderLayout = new LinearLayout(header.getContext()); 273 | if (orientation == LinearLayout.VERTICAL) { 274 | mHeaderLayout.setOrientation(LinearLayout.VERTICAL); 275 | mHeaderLayout.setLayoutParams(new RecyclerView.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); 276 | } else { 277 | mHeaderLayout.setOrientation(LinearLayout.HORIZONTAL); 278 | mHeaderLayout.setLayoutParams(new RecyclerView.LayoutParams(WRAP_CONTENT, MATCH_PARENT)); 279 | } 280 | } 281 | final int childCount = mHeaderLayout.getChildCount(); 282 | if (index < 0 || index > childCount) { 283 | index = childCount; 284 | } 285 | mHeaderLayout.addView(header, index); 286 | if (mHeaderLayout.getChildCount() == 1) { 287 | notifyItemInserted(0); 288 | } 289 | return index; 290 | } 291 | 292 | public void removeHeaderView(View header) { 293 | if (getHeaderLayoutCount() == 0) { 294 | return; 295 | } 296 | mHeaderLayout.removeView(header); 297 | if (mHeaderLayout.getChildCount() == 0) { 298 | notifyItemRemoved(0); 299 | } 300 | } 301 | 302 | /** 303 | * Append footer to the rear of the mFooterLayout. 304 | * 305 | * @param footer 306 | */ 307 | public int addFooterView(View footer) { 308 | return addFooterView(footer, -1, LinearLayout.VERTICAL); 309 | } 310 | 311 | public int addFooterView(View footer, int index) { 312 | return addFooterView(footer, index, LinearLayout.VERTICAL); 313 | } 314 | 315 | /** 316 | * Add footer view to mFooterLayout and set footer view position in mFooterLayout. 317 | * When index = -1 or index >= child count in mFooterLayout, 318 | * the effect of this method is the same as that of {@link #addFooterView(View)}. 319 | * 320 | * @param footer 321 | * @param index the position in mFooterLayout of this footer. 322 | * When index = -1 or index >= child count in mFooterLayout, 323 | * the effect of this method is the same as that of {@link #addFooterView(View)}. 324 | */ 325 | public int addFooterView(View footer, int index, int orientation) { 326 | if (mFooterLayout == null) { 327 | mFooterLayout = new LinearLayout(footer.getContext()); 328 | if (orientation == LinearLayout.VERTICAL) { 329 | mFooterLayout.setOrientation(LinearLayout.VERTICAL); 330 | mFooterLayout.setLayoutParams(new RecyclerView.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); 331 | } else { 332 | mFooterLayout.setOrientation(LinearLayout.HORIZONTAL); 333 | mFooterLayout.setLayoutParams(new RecyclerView.LayoutParams(WRAP_CONTENT, MATCH_PARENT)); 334 | } 335 | } 336 | final int childCount = mFooterLayout.getChildCount(); 337 | if (index < 0 || index > childCount) { 338 | index = childCount; 339 | } 340 | mFooterLayout.addView(footer, index); 341 | if (mFooterLayout.getChildCount() == 1) { 342 | int position = getHeaderLayoutCount() + getItemSize(); 343 | if (position != -1) { 344 | notifyItemInserted(position); 345 | } 346 | } 347 | return index; 348 | } 349 | 350 | public void removeFooterView(View footer) { 351 | if (getFooterLayoutCount() == 0) return; 352 | 353 | mFooterLayout.removeView(footer); 354 | if (mFooterLayout.getChildCount() == 0) { 355 | int position = getHeaderLayoutCount() + getItemSize(); 356 | if (position != -1) { 357 | notifyItemRemoved(position); 358 | } 359 | } 360 | } 361 | 362 | /** 363 | * if addHeaderView will be return 1, if not will be return 0 364 | */ 365 | public int getHeaderLayoutCount() { 366 | if (mHeaderLayout == null || mHeaderLayout.getChildCount() == 0) { 367 | return 0; 368 | } 369 | return 1; 370 | } 371 | 372 | /** 373 | * if addFooterView will be return 1, if not will be return 0 374 | */ 375 | public int getFooterLayoutCount() { 376 | if (mFooterLayout == null || mFooterLayout.getChildCount() == 0) { 377 | return 0; 378 | } 379 | return 1; 380 | } 381 | 382 | /** 383 | * Load more view count 384 | * 385 | * @return 0 or 1 386 | */ 387 | public int getLoadMoreViewCount() { 388 | if (!mLoadMoreEnable) { 389 | return 0; 390 | } 391 | if (loadMoreStatus == LOAD_MORE_NO_MORE) { 392 | return 0; 393 | } 394 | if (getItemSize() == 0) { 395 | return 0; 396 | } 397 | return 1; 398 | } 399 | 400 | public boolean isHeadLayout(int position) { 401 | return getItemViewType(position) == HEADER_VIEW; 402 | } 403 | 404 | public boolean isLoadMoreLayout(int position) { 405 | return getItemViewType(position) == LOADING_VIEW; 406 | } 407 | 408 | public boolean isFooterLayout(int position) { 409 | return getItemViewType(position) == FOOTER_VIEW; 410 | } 411 | 412 | /** 413 | * 加载更多完成 414 | */ 415 | public void loadMoreComplete() { 416 | setLoadMoreStatus(LOAD_MORE_NORMAL); 417 | } 418 | 419 | /** 420 | * 加载更多失败 421 | */ 422 | public void loadMoreFail() { 423 | setLoadMoreStatus(LOAD_MORE_FAIL); 424 | } 425 | 426 | /** 427 | * 加载到底部了 428 | */ 429 | public void loadMoreEnd() { 430 | setLoadMoreStatus(LOAD_MORE_NO_MORE); 431 | } 432 | 433 | public void setLoadMoreStatus(int status) { 434 | if (getLoadMoreViewCount() == 0) { 435 | return; 436 | } 437 | isLoading = false; 438 | loadMoreStatus = status; 439 | notifyItemChanged(getHeaderLayoutCount() + getItemSize() + getFooterLayoutCount()); 440 | } 441 | 442 | public void resetLoadMoreStatus() { 443 | loadMoreStatus = LOAD_MORE_NORMAL; 444 | } 445 | 446 | @Override 447 | protected boolean canLoadMore() { 448 | return loadMoreStatus == LOAD_MORE_NORMAL; 449 | } 450 | 451 | public static final int LOAD_MORE_NORMAL = 0; //正常情况 452 | public static final int LOAD_MORE_FAIL = 1; //失败 453 | public static final int LOAD_MORE_NO_MORE = 2; //没有更多 454 | 455 | private int loadMoreStatus = LOAD_MORE_NORMAL; 456 | 457 | public int getLoadMoreStatus() { 458 | return loadMoreStatus; 459 | } 460 | 461 | private void setLoadMoreView(LoadMoreViewHolder viewHolder) { 462 | switch (loadMoreStatus) { 463 | case LOAD_MORE_NORMAL: 464 | viewHolder.progressBar.setVisibility(View.VISIBLE); 465 | viewHolder.tv_status.setText("加载更多中..."); 466 | viewHolder.itemView.setVisibility(View.VISIBLE); 467 | break; 468 | case LOAD_MORE_FAIL: 469 | viewHolder.progressBar.setVisibility(View.GONE); 470 | viewHolder.tv_status.setText("加载更多失败,点击重试"); 471 | 472 | viewHolder.itemView.setVisibility(View.VISIBLE); 473 | break; 474 | case LOAD_MORE_NO_MORE: 475 | viewHolder.progressBar.setVisibility(View.GONE); 476 | viewHolder.tv_status.setText("已经到底部了"); 477 | viewHolder.itemView.setVisibility(View.VISIBLE); 478 | break; 479 | } 480 | } 481 | 482 | private static class LoadMoreViewHolder extends RecyclerView.ViewHolder { 483 | private ProgressBar progressBar; 484 | private TextView tv_status; 485 | 486 | public LoadMoreViewHolder(View itemView) { 487 | super(itemView); 488 | } 489 | } 490 | 491 | private static class HeadViewHolder extends RecyclerView.ViewHolder { 492 | 493 | public HeadViewHolder(View itemView) { 494 | super(itemView); 495 | } 496 | } 497 | 498 | private static class FootViewHolder extends RecyclerView.ViewHolder { 499 | 500 | public FootViewHolder(View itemView) { 501 | super(itemView); 502 | } 503 | } 504 | } 505 | -------------------------------------------------------------------------------- /app/src/main/java/view/gavin/com/flexibleview/adapter/MoreItemHelper.java: -------------------------------------------------------------------------------- 1 | package view.gavin.com.flexibleview.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * Created by think on 17/8/3. 13 | */ 14 | 15 | public abstract class MoreItemHelper { 16 | protected List list; 17 | protected Context context; 18 | 19 | public MoreItemHelper(List list) { 20 | this.list = list; 21 | } 22 | 23 | /** 24 | * @return item 类型 25 | */ 26 | public abstract int getItemType(); 27 | 28 | public RecyclerView.ViewHolder createViewHolder(Context context, ViewGroup parent) { 29 | if (this.context == null) { 30 | this.context = context; 31 | } 32 | View view = LayoutInflater.from(context).inflate(getLayoutId(), parent, false); 33 | return getViewHolder(view); 34 | } 35 | 36 | /** 37 | * @return item 布局 38 | */ 39 | public abstract int getLayoutId(); 40 | 41 | public abstract RecyclerView.ViewHolder getViewHolder(View view); 42 | 43 | /** 44 | * @return item 个数 45 | */ 46 | public int getItemCount() { 47 | return list != null ? list.size() : 0; 48 | } 49 | 50 | /** 51 | * 绑定视图 52 | */ 53 | public abstract void onBindView(RecyclerView.ViewHolder holder, int position); 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/view/gavin/com/flexibleview/adapter/SimpleListAdapter.java: -------------------------------------------------------------------------------- 1 | package view.gavin.com.flexibleview.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | import android.widget.TextView; 8 | 9 | import java.util.List; 10 | 11 | import view.gavin.com.flexibleview.R; 12 | 13 | /** 14 | * Created by gavin 15 | * date 2018/6/18 16 | */ 17 | public class SimpleListAdapter extends BaseRecyclerViewAdapter { 18 | public SimpleListAdapter(Context context, @NonNull List list) { 19 | super(context, list); 20 | } 21 | 22 | @Override 23 | protected int getLayoutId() { 24 | return R.layout.item_recycler_view; 25 | } 26 | 27 | @NonNull 28 | @Override 29 | protected RecyclerView.ViewHolder getViewHolder(View view) { 30 | return new Holder(view); 31 | } 32 | 33 | @Override 34 | protected void onBindView(RecyclerView.ViewHolder viewHolder, int position) { 35 | Holder holder = (Holder) viewHolder; 36 | ((TextView)holder.itemView).setText(list.get(position)); 37 | } 38 | 39 | class Holder extends RecyclerView.ViewHolder { 40 | 41 | public Holder(View itemView) { 42 | super(itemView); 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/header_background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gavin-ZYX/FlexibleLayout/2185b81f1d72f6940377711b5aa816a5642f2231/app/src/main/res/drawable/header_background.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_coordinator_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 18 | 19 | 22 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 |