├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── cc │ │ └── library │ │ └── view │ │ ├── DragSwipeDemoAct.java │ │ ├── MenuAct.java │ │ ├── PtrSwipeDemoAct.java │ │ ├── adapter │ │ ├── CustomAdapter.java │ │ ├── DragSwipeAdapter.java │ │ └── SwipeAdapter.java │ │ ├── base │ │ └── BaseApplication.java │ │ ├── util │ │ ├── DisplayUtils.java │ │ └── ToastUtil.java │ │ └── widget │ │ ├── MFooterView.java │ │ └── MHeaderView.java │ └── res │ ├── layout │ ├── activity_menu.xml │ ├── activity_swipe_demo.xml │ ├── adapter_recyclerview_menu_demo.xml │ ├── content_view_for_test.xml │ ├── m_footer_view.xml │ ├── m_header_view.xml │ ├── menu1_for_test.xml │ └── menu_for_test.xml │ ├── menu │ └── prt_act_menu.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── loadmore.jpg ├── refreshing.jpg └── swipemenu.jpg ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── library │ │ └── widget │ │ ├── adapter │ │ └── SwipeMenuAdapter.java │ │ ├── baseview │ │ ├── FooterView.java │ │ ├── HeaderView.java │ │ ├── PtrSwipeMenuRecyclerView.java │ │ └── SwipeMenuLayout.java │ │ ├── defaultimple │ │ ├── DefaultFooterView.java │ │ └── DefaultHeaderView.java │ │ └── interfaces │ │ └── OnMenuClickListener.java │ └── res │ ├── layout │ ├── pull_to_refresh_footer.xml │ └── pull_to_refresh_header.xml │ └── values │ └── strings.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ###PtrSwipeMenuRecyclerView 2 | --- 3 | ######本项目对RecyclerView进行再次封装,集合了最常用的下拉刷新、上拉加载更多和侧滑菜单功能。 4 | ######另,项目中也利用ItemTouchHelper简单实现了对item的拖拽操作,长按可对item进行拖拽移动 5 | 6 | >项目在RecyclerView的基础上实现了侧滑菜单、下拉刷新、滚动到底部自动加载更多的功能,支持自定义下拉刷新和加载更多的头部HeaderView、底 7 | 部FooterView,侧滑菜单可根据自身业务给不同的item设置不同的侧滑菜单,或作根据item的具体数据情况决定是否设置侧滑菜 8 | 单。 相关效果图等详见下面的说明。 9 | 10 | **1.效果图片** 11 | 12 | 侧滑菜单效果: 13 | 14 | ![image](https://github.com/zhangyuChen1991/some_sources/blob/master/ptrSwipeRecyclerView/swipe.gif) 15 | 16 | 下拉刷新效果: 17 | 18 | ![image](https://github.com/zhangyuChen1991/some_sources/blob/master/ptrSwipeRecyclerView/pull-down.gif) 19 | 20 | 上拉加载效果: 21 | 22 | ![image](https://github.com/zhangyuChen1991/some_sources/blob/master/ptrSwipeRecyclerView/pull-up.gif) 23 | 24 | 拖拽效果: 25 | 26 | ![image](https://github.com/zhangyuChen1991/some_sources/blob/master/ptrSwipeRecyclerView/drag.gif) 27 | 28 | 29 | 30 | **2.使用说明** 31 | > 在所需引用此库的module的build.gradle文件中添加: 32 | ``` 33 | dependencies { 34 | compile 'com.library.widget:library:1.0.1' 35 | } 36 | ``` 37 | 38 | 注:项目功能为初步实现,仍在改进中。欢迎提出意见,共同讨论共同进步! -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.0" 6 | defaultConfig { 7 | applicationId "com.cc.library.view" 8 | minSdkVersion 15 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 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 fileTree(include: ['*.jar'], dir: 'libs') 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:25.0.0' 28 | testCompile 'junit:junit:4.12' 29 | compile project(':library') 30 | } 31 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\czyWorkSpace\envirment\android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/cc/library/view/DragSwipeDemoAct.java: -------------------------------------------------------------------------------- 1 | package com.cc.library.view; 2 | 3 | import android.os.AsyncTask; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.support.v7.widget.helper.ItemTouchHelper; 9 | import android.view.View; 10 | 11 | import com.cc.library.view.adapter.DragSwipeAdapter; 12 | import com.cc.library.view.util.ToastUtil; 13 | import com.library.widget.baseview.PtrSwipeMenuRecyclerView; 14 | import com.library.widget.interfaces.OnMenuClickListener; 15 | 16 | import java.util.ArrayList; 17 | import java.util.Collections; 18 | import java.util.List; 19 | 20 | /** 21 | * Created by zhangyu on 2016/11/17. 22 | */ 23 | 24 | public class DragSwipeDemoAct extends AppCompatActivity { 25 | private PtrSwipeMenuRecyclerView recyclerView; 26 | private DragSwipeAdapter adapter; 27 | public List datas; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_swipe_demo); 33 | initResources(); 34 | initView(); 35 | } 36 | 37 | @Override 38 | public boolean onSupportNavigateUp() { 39 | finish(); 40 | return super.onSupportNavigateUp(); 41 | } 42 | 43 | private void initResources() { 44 | adapter = new DragSwipeAdapter(); 45 | datas = new ArrayList<>(); 46 | for (int i = 0; i < 17; i++) { 47 | datas.add(i); 48 | } 49 | adapter.setDatas(datas); 50 | } 51 | 52 | private void initView() { 53 | initActionBar(); 54 | 55 | recyclerView = (PtrSwipeMenuRecyclerView) findViewById(R.id.recycler_view); 56 | //参数:context,横向或纵向滑动,是否颠倒显示数据 57 | recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); 58 | recyclerView.setAdapter(adapter); 59 | // //设置不允许上拉加载更多 60 | // recyclerView.setPullLoadMoreEnable(false); 61 | // //设置不允许下拉刷新 62 | recyclerView.setPullToRefreshEnable(false); 63 | //添加菜单点击监听事件 64 | recyclerView.setOnMenuClickListener(onMenuClickListener); 65 | recyclerView.setOnPullListener(onPullListener); 66 | itemTouchHelper.attachToRecyclerView(recyclerView); 67 | 68 | } 69 | 70 | private void initActionBar() { 71 | android.support.v7.app.ActionBar actionBar = getSupportActionBar(); 72 | actionBar.setDisplayHomeAsUpEnabled(true); 73 | } 74 | 75 | 76 | /** 77 | * 菜单点击事件监听 78 | */ 79 | private OnMenuClickListener onMenuClickListener = new OnMenuClickListener() { 80 | @Override 81 | public void onMenuClick(View view, int position) { 82 | if (view.getId() == R.id.menu1) 83 | ToastUtil.showToast("position:" + position + ",menu1", 0); 84 | if (view.getId() == R.id.menu2) 85 | ToastUtil.showToast("position:" + position + ",menu2", 0); 86 | } 87 | }; 88 | 89 | /** 90 | * 上拉加载、下拉刷新监听 91 | */ 92 | private PtrSwipeMenuRecyclerView.OnPullListener onPullListener = new PtrSwipeMenuRecyclerView.OnPullListener() { 93 | @Override 94 | public void onRefresh() { 95 | new GetDataTask().execute(); 96 | } 97 | 98 | @Override 99 | public void onLoadMore() { 100 | new LoadMoreTask().execute(); 101 | } 102 | }; 103 | 104 | 105 | /** 106 | * 使用itemTouchHelper完成拖拽效果 107 | * 由于拖拽与下拉刷新有冲突,所以暂时不建议放在一起使用 108 | */ 109 | private ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() { 110 | @Override 111 | public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { 112 | final int dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN; 113 | return makeMovementFlags(dragFlag, 0); 114 | } 115 | 116 | @Override 117 | public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { 118 | 119 | int from = viewHolder.getAdapterPosition(); 120 | int to = target.getAdapterPosition(); 121 | if (to < datas.size() - 1) {//把Footerview的位置减掉(ViewType不一样) 122 | Collections.swap(datas, from, to); 123 | adapter.notifyItemMoved(from, to); 124 | return true; 125 | } 126 | return false; 127 | } 128 | 129 | @Override 130 | public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { 131 | 132 | } 133 | 134 | @Override 135 | public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { 136 | DragSwipeAdapter.ViewHolder holder = (DragSwipeAdapter.ViewHolder) viewHolder; 137 | //被选中时,改背景色 138 | if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { 139 | holder.tv.setBackgroundColor(getResources().getColor(R.color.deepskyblue_pressed)); 140 | } 141 | super.onSelectedChanged(viewHolder, actionState); 142 | } 143 | 144 | @Override 145 | public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { 146 | super.clearView(recyclerView, viewHolder); 147 | DragSwipeAdapter.ViewHolder holder = (DragSwipeAdapter.ViewHolder) viewHolder; 148 | holder.tv.setBackgroundColor(getResources().getColor(R.color.deepskyblue)); 149 | } 150 | }); 151 | 152 | private class GetDataTask extends AsyncTask { 153 | 154 | @Override 155 | protected String[] doInBackground(Void... params) { 156 | try { 157 | Thread.sleep(3000); 158 | } catch (InterruptedException e) { 159 | } 160 | return null; 161 | } 162 | 163 | @Override 164 | protected void onPostExecute(String[] result) { 165 | recyclerView.onRefreshComplete(); 166 | super.onPostExecute(result); 167 | } 168 | } 169 | 170 | private class LoadMoreTask extends AsyncTask { 171 | 172 | @Override 173 | protected String[] doInBackground(Void... params) { 174 | try { 175 | Thread.sleep(2000); 176 | } catch (InterruptedException e) { 177 | } 178 | return null; 179 | } 180 | 181 | @Override 182 | protected void onPostExecute(String[] result) { 183 | int count = datas.size(); 184 | for (int i = count; i < count + 10; i++) { 185 | datas.add(i); 186 | } 187 | adapter.setDatas(datas); 188 | adapter.notifyDataSetChanged(); 189 | recyclerView.onLoadMoreComplete(); 190 | super.onPostExecute(result); 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /app/src/main/java/com/cc/library/view/MenuAct.java: -------------------------------------------------------------------------------- 1 | package com.cc.library.view; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | 10 | import com.cc.library.view.adapter.CustomAdapter; 11 | 12 | 13 | /** 14 | * Created by zhangyu on 2016/10/31. 15 | */ 16 | 17 | public class MenuAct extends AppCompatActivity { 18 | private static final String TAG = "RVDMenu"; 19 | private RecyclerView recyclerView; 20 | private CustomAdapter adapter = new CustomAdapter(); 21 | private String[] data = {"下拉刷新、自动加载及侧滑菜单Demo", "长按拖拽Item及侧滑菜单Demo"}; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_menu); 27 | initView(); 28 | initResources(); 29 | } 30 | 31 | private void initView() { 32 | recyclerView = (RecyclerView) findViewById(R.id.arvdm_recycler_view); 33 | //参数:context,横向或纵向滑动,是否颠倒显示数据 34 | recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); 35 | recyclerView.setAdapter(adapter); 36 | } 37 | 38 | /** 39 | * 点击事件监听 40 | */ 41 | private CustomAdapter.RecyclerViewOnClickListener recyclerViewOnClick = new CustomAdapter.RecyclerViewOnClickListener() { 42 | @Override 43 | public void onItemClick(int position) { 44 | switch (position){ 45 | case 0: 46 | startActivity(new Intent(MenuAct.this,PtrSwipeDemoAct.class)); 47 | break; 48 | case 1: 49 | startActivity(new Intent(MenuAct.this,DragSwipeDemoAct.class)); 50 | break; 51 | } 52 | } 53 | }; 54 | 55 | private void initResources() { 56 | adapter.setData(data); 57 | adapter.setRecyclerViewOnClick(recyclerViewOnClick); 58 | adapter.notifyDataSetChanged(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/cc/library/view/PtrSwipeDemoAct.java: -------------------------------------------------------------------------------- 1 | package com.cc.library.view; 2 | 3 | import android.os.AsyncTask; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.view.Menu; 8 | import android.view.MenuInflater; 9 | import android.view.MenuItem; 10 | import android.view.View; 11 | 12 | import com.cc.library.view.adapter.SwipeAdapter; 13 | import com.cc.library.view.util.ToastUtil; 14 | import com.cc.library.view.widget.MFooterView; 15 | import com.cc.library.view.widget.MHeaderView; 16 | import com.library.widget.baseview.FooterView; 17 | import com.library.widget.baseview.HeaderView; 18 | import com.library.widget.baseview.PtrSwipeMenuRecyclerView; 19 | import com.library.widget.interfaces.OnMenuClickListener; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | public class PtrSwipeDemoAct extends AppCompatActivity { 25 | private PtrSwipeMenuRecyclerView recyclerView; 26 | private SwipeAdapter swipeAdapter; 27 | public List datas; 28 | private MHeaderView mHeaderView; 29 | private MFooterView mFooterView; 30 | 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_swipe_demo); 35 | initResources(); 36 | initView(); 37 | 38 | } 39 | 40 | @Override 41 | public boolean onSupportNavigateUp() { 42 | finish(); 43 | return super.onSupportNavigateUp(); 44 | } 45 | 46 | private void initResources() { 47 | mHeaderView = new MHeaderView(this); 48 | mFooterView = new MFooterView(this); 49 | initAdapter(null, null); 50 | } 51 | 52 | private void initAdapter(HeaderView headerView, FooterView footerView) { 53 | swipeAdapter = new SwipeAdapter(); 54 | 55 | //不设置Header和Footer则会使用默认的布局 56 | if (headerView != null) 57 | swipeAdapter.setHeaderView(headerView); 58 | if (footerView != null) 59 | swipeAdapter.setFooterView(footerView); 60 | datas = new ArrayList<>(); 61 | for (int i = 0; i < 17; i++) { 62 | datas.add(i); 63 | } 64 | swipeAdapter.setDatas(datas); 65 | } 66 | 67 | private void initView() { 68 | initActionBar(); 69 | 70 | recyclerView = (PtrSwipeMenuRecyclerView) findViewById(R.id.recycler_view); 71 | //参数:context,横向或纵向滑动,是否颠倒显示数据 72 | recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); 73 | recyclerView.setAdapter(swipeAdapter); 74 | // //设置不允许上拉加载更多 75 | // recyclerView.setPullLoadMoreEnable(false); 76 | // //设置不允许下拉刷新 77 | // recyclerView.setPullToRefreshEnable(false); 78 | //添加菜单点击监听事件 79 | recyclerView.setOnMenuClickListener(onMenuClickListener); 80 | recyclerView.setOnPullListener(onPullListener); 81 | 82 | } 83 | 84 | private void initActionBar() { 85 | android.support.v7.app.ActionBar actionBar = getSupportActionBar(); 86 | actionBar.setDisplayHomeAsUpEnabled(true); 87 | } 88 | 89 | 90 | /** 91 | * 菜单点击事件监听 92 | */ 93 | private OnMenuClickListener onMenuClickListener = new OnMenuClickListener() { 94 | @Override 95 | public void onMenuClick(View view, int position) { 96 | if (view.getId() == R.id.menu1) 97 | ToastUtil.showToast("position:" + position + ",menu1", 0); 98 | if (view.getId() == R.id.menu2) 99 | ToastUtil.showToast("position:" + position + ",menu2", 0); 100 | } 101 | }; 102 | 103 | /** 104 | * 上拉加载、下拉刷新监听 105 | */ 106 | private PtrSwipeMenuRecyclerView.OnPullListener onPullListener = new PtrSwipeMenuRecyclerView.OnPullListener() { 107 | @Override 108 | public void onRefresh() { 109 | new GetDataTask().execute(); 110 | } 111 | 112 | @Override 113 | public void onLoadMore() { 114 | new LoadMoreTask().execute(); 115 | } 116 | }; 117 | 118 | private class GetDataTask extends AsyncTask { 119 | 120 | @Override 121 | protected String[] doInBackground(Void... params) { 122 | try { 123 | Thread.sleep(3000); 124 | } catch (InterruptedException e) { 125 | } 126 | return null; 127 | } 128 | 129 | @Override 130 | protected void onPostExecute(String[] result) { 131 | recyclerView.onRefreshComplete(); 132 | super.onPostExecute(result); 133 | } 134 | } 135 | 136 | private class LoadMoreTask extends AsyncTask { 137 | 138 | @Override 139 | protected String[] doInBackground(Void... params) { 140 | try { 141 | Thread.sleep(2000); 142 | } catch (InterruptedException e) { 143 | } 144 | return null; 145 | } 146 | 147 | @Override 148 | protected void onPostExecute(String[] result) { 149 | int count = datas.size(); 150 | for (int i = count; i < count + 10; i++) { 151 | datas.add(i); 152 | } 153 | swipeAdapter.setDatas(datas); 154 | swipeAdapter.notifyDataSetChanged(); 155 | recyclerView.onLoadMoreComplete(); 156 | super.onPostExecute(result); 157 | } 158 | } 159 | 160 | @Override 161 | public boolean onCreateOptionsMenu(Menu menu) { 162 | MenuInflater menuInflater = getMenuInflater(); 163 | menuInflater.inflate(R.menu.prt_act_menu, menu); 164 | return super.onCreateOptionsMenu(menu); 165 | } 166 | 167 | @Override 168 | public boolean onOptionsItemSelected(MenuItem item) { 169 | int itemId = item.getItemId(); 170 | switch (itemId) { 171 | case R.id.change_header_footer: 172 | initAdapter(mHeaderView, mFooterView);//重置adapter,改变header和footer 173 | recyclerView.setAdapter(swipeAdapter); 174 | ToastUtil.showToast("Header&Footer已改变", 0); 175 | break; 176 | } 177 | return super.onOptionsItemSelected(item); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /app/src/main/java/com/cc/library/view/adapter/CustomAdapter.java: -------------------------------------------------------------------------------- 1 | package com.cc.library.view.adapter; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.TextView; 7 | 8 | import com.cc.library.view.R; 9 | 10 | 11 | /** 12 | * RecyclerView使用的Adapter 13 | * 1.需要手动实现ViewHolder类(这里写成内部类) 14 | * 2.实现RecyclerView.Adapter的相关抽象方法:onCreateViewHolder()、onBindViewHolder()、getItemCount() 15 | * 3.额外实现了点击事件的监听 16 | * Created by zhangyu on 2016/10/31. 17 | */ 18 | 19 | public class CustomAdapter extends RecyclerView.Adapter { 20 | 21 | private RecyclerViewOnClickListener recyclerViewOnClick; 22 | private String[] data; 23 | 24 | //RecyclerView需要手动实现一个ViewHolder ,跟ListView写ViewHolder差不多样 25 | protected class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 26 | 27 | public TextView tv; 28 | public View itemView; 29 | 30 | public ViewHolder(View itemView) { 31 | super(itemView); 32 | this.itemView = itemView; 33 | tv = (TextView) itemView.findViewById(R.id.ard_tv); 34 | 35 | //设置点击监听 36 | this.itemView.setOnClickListener(this); 37 | } 38 | 39 | @Override 40 | public void onClick(View v) { 41 | //用getAdapterPosition获取item在adapter中的位置 42 | if (v.getId() == itemView.getId()) { 43 | if (null != recyclerViewOnClick) 44 | recyclerViewOnClick.onItemClick(getAdapterPosition()); 45 | } 46 | } 47 | } 48 | 49 | //设置ViewHolder 把需要绑定的布局传进去 50 | @Override 51 | public CustomAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 52 | View itemView = View.inflate(parent.getContext(), R.layout.adapter_recyclerview_menu_demo, null); 53 | return new ViewHolder(itemView); 54 | } 55 | 56 | //onBind 给布局设置数据 57 | // 同时添加点击事件监听(RecyclerView 默认未设置点击监听) 58 | @Override 59 | public void onBindViewHolder(ViewHolder holder, int position) { 60 | 61 | if (data != null && position < data.length) 62 | holder.tv.setText(data[position]); 63 | 64 | } 65 | 66 | 67 | //item数目 68 | @Override 69 | public int getItemCount() { 70 | return data == null ? 0 : data.length; 71 | } 72 | 73 | public void setData(String[] data) { 74 | this.data = data; 75 | } 76 | 77 | public void setRecyclerViewOnClick(RecyclerViewOnClickListener recyclerViewOnClick) { 78 | this.recyclerViewOnClick = recyclerViewOnClick; 79 | } 80 | 81 | @Override 82 | public int getItemViewType(int position) { 83 | return super.getItemViewType(position); 84 | } 85 | 86 | /** 87 | * 监听回调接口 88 | */ 89 | public interface RecyclerViewOnClickListener { 90 | void onItemClick(int position); 91 | 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/com/cc/library/view/adapter/DragSwipeAdapter.java: -------------------------------------------------------------------------------- 1 | package com.cc.library.view.adapter; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.LinearLayout; 8 | import android.widget.TextView; 9 | 10 | import com.cc.library.view.R; 11 | import com.library.widget.baseview.PtrSwipeMenuRecyclerView; 12 | import com.library.widget.adapter.SwipeMenuAdapter; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * Created by zhangyu on 2016/11/9. 18 | */ 19 | public class DragSwipeAdapter extends SwipeMenuAdapter { 20 | public List datas; 21 | 22 | public class ViewHolder extends PtrSwipeMenuRecyclerView.ViewHolder { 23 | public TextView tv; 24 | 25 | public ViewHolder(View itemView) { 26 | super(itemView); 27 | tv = (TextView) itemView.findViewById(R.id.content_tv); 28 | } 29 | } 30 | 31 | @Override 32 | protected View createContentView(ViewGroup parent, int viewType) { 33 | View contentView = LayoutInflater.from(parent.getContext()).inflate(R.layout.content_view_for_test, parent, false); 34 | return contentView; 35 | } 36 | 37 | @Override 38 | protected LinearLayout createMenuView(ViewGroup parent, int viewType) { 39 | LinearLayout menuView = (LinearLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout.menu_for_test, parent, false); 40 | 41 | return menuView; 42 | } 43 | 44 | @Override 45 | public RecyclerView.ViewHolder onCreateThisViewHolder(ViewGroup contentView, int viewType) { 46 | return new ViewHolder(contentView); 47 | } 48 | 49 | @Override 50 | public void onBindThisViewHolder(ViewHolder holder, int position) { 51 | if (null != datas && position < datas.size()){ 52 | holder.tv.setText("content" + datas.get(position)); 53 | } 54 | 55 | } 56 | 57 | @Override 58 | public int getThisItemCount() { 59 | return datas == null ? 0 : datas.size(); 60 | } 61 | 62 | public void setDatas(List datas) { 63 | this.datas = datas; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/cc/library/view/adapter/SwipeAdapter.java: -------------------------------------------------------------------------------- 1 | package com.cc.library.view.adapter; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.LinearLayout; 8 | import android.widget.TextView; 9 | 10 | import com.cc.library.view.R; 11 | import com.library.widget.baseview.PtrSwipeMenuRecyclerView; 12 | import com.library.widget.adapter.SwipeMenuAdapter; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * Created by zhangyu on 2016/11/9. 18 | */ 19 | public class SwipeAdapter extends SwipeMenuAdapter { 20 | public List datas; 21 | public final int ONE_MENU = 0X1132, NO_MENU = 0X1133, TWO_MENU = 0X1134; 22 | 23 | public class ViewHolder extends PtrSwipeMenuRecyclerView.ViewHolder { 24 | public TextView tv; 25 | 26 | public ViewHolder(View itemView) { 27 | super(itemView); 28 | tv = (TextView) itemView.findViewById(R.id.content_tv); 29 | } 30 | } 31 | 32 | @Override 33 | protected View createContentView(ViewGroup parent, int viewType) { 34 | View contentView = LayoutInflater.from(parent.getContext()).inflate(R.layout.content_view_for_test, parent, false); 35 | return contentView; 36 | } 37 | 38 | @Override 39 | protected LinearLayout createMenuView(ViewGroup parent, int viewType) { 40 | LinearLayout menuView = (LinearLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout.menu_for_test, parent, false); 41 | LinearLayout menuView1 = (LinearLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout.menu1_for_test, parent, false); 42 | 43 | if (viewType == ONE_MENU) 44 | return menuView1; 45 | else if (viewType == TWO_MENU) 46 | return menuView; 47 | else 48 | return null; 49 | } 50 | 51 | @Override 52 | public RecyclerView.ViewHolder onCreateThisViewHolder(ViewGroup contentView, int viewType) { 53 | return new ViewHolder(contentView); 54 | } 55 | 56 | @Override 57 | public void onBindThisViewHolder(ViewHolder holder, int position) { 58 | if (null != datas && position < datas.size()){ 59 | if (position % 3 == 0) { 60 | holder.tv.setText("我没有菜单 position:" + datas.get(position)); 61 | } else if (position % 3 == 1) { 62 | holder.tv.setText("我有一个菜单 position:" + datas.get(position)); 63 | } else 64 | holder.tv.setText("我有两个菜单 position:" + datas.get(position)); 65 | } 66 | 67 | } 68 | 69 | @Override 70 | public int getThisItemViewType(int position) { 71 | if (position % 3 == 0) { 72 | return NO_MENU; 73 | } else if (position % 3 == 1) { 74 | return ONE_MENU; 75 | } else 76 | return TWO_MENU; 77 | } 78 | 79 | 80 | @Override 81 | public int getThisItemCount() { 82 | return datas == null ? 0 : datas.size(); 83 | } 84 | 85 | public void setDatas(List datas) { 86 | this.datas = datas; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/com/cc/library/view/base/BaseApplication.java: -------------------------------------------------------------------------------- 1 | package com.cc.library.view.base; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | /** 7 | * Created by zhangyu on 2016-07-11 18:49. 8 | */ 9 | public class BaseApplication extends Application { 10 | public static Context context; 11 | @Override 12 | public void onCreate() { 13 | super.onCreate(); 14 | context = getApplicationContext(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/cc/library/view/util/DisplayUtils.java: -------------------------------------------------------------------------------- 1 | package com.cc.library.view.util; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.res.Resources; 7 | import android.graphics.Rect; 8 | import android.os.Build; 9 | import android.util.DisplayMetrics; 10 | import android.util.Log; 11 | import android.view.Display; 12 | import android.view.Window; 13 | import android.view.WindowManager; 14 | 15 | import com.cc.library.view.base.BaseApplication; 16 | 17 | import java.lang.reflect.Field; 18 | import java.lang.reflect.Method; 19 | 20 | /** 21 | * @author 22 | * @创建时间:2016-3-9下午5:06:06 23 | * @描述: dip/dp、sp 转换为 px 的工具类 24 | */ 25 | public class DisplayUtils { 26 | 27 | /** 28 | * 将px值转换为dip或dp值,保证尺寸大小不变 29 | * 30 | * @param pxValue 31 | * @return 32 | */ 33 | public static int px2dip(Context context, float pxValue) { 34 | final float scale = context.getResources().getDisplayMetrics().density; 35 | return (int) (pxValue / scale + 0.5f); 36 | } 37 | 38 | /** 39 | * 将dip或dp值转换为px值,保证尺寸大小不变 40 | * 41 | * @param dipValue 42 | * @return 43 | */ 44 | public static int dip2px(Context context, float dipValue) { 45 | final float scale = context.getResources().getDisplayMetrics().density; 46 | return (int) (dipValue * scale + 0.5f); 47 | } 48 | 49 | /** 50 | * 将px值转换为sp值,保证文字大小不变 51 | * 52 | * @param pxValue 53 | * @return 54 | */ 55 | public static int px2sp(Context context, float pxValue) { 56 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 57 | return (int) (pxValue / fontScale + 0.5f); 58 | } 59 | 60 | /** 61 | * 将sp值转换为px值,保证文字大小不变 62 | * 63 | * @param spValue 64 | * @return 65 | */ 66 | public static int sp2px(Context context, float spValue) { 67 | final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 68 | return (int) (spValue * fontScale + 0.5f); 69 | } 70 | 71 | // 设置屏幕透明度 72 | public static void setBackgroundAlpha(Activity context, float bgAlpha) { 73 | WindowManager.LayoutParams lp = context.getWindow().getAttributes(); 74 | lp.alpha = bgAlpha; // 0.0~1.0 75 | context.getWindow().setAttributes(lp); 76 | } 77 | 78 | /** 79 | * 获得设备的屏幕宽 80 | */ 81 | public static int getWidth() { 82 | Context context = BaseApplication.context; 83 | if (null == context) { 84 | return 0; 85 | } 86 | DisplayMetrics dm = null; 87 | try { 88 | if (context != null) { 89 | dm = new DisplayMetrics(); 90 | dm = context.getApplicationContext().getResources() 91 | .getDisplayMetrics(); 92 | } 93 | 94 | return dm.widthPixels; 95 | } catch (Exception e) { 96 | e.printStackTrace(); 97 | } 98 | return 0; 99 | } 100 | 101 | /** 102 | * 获得设备的屏幕高 103 | */ 104 | public static int getHeight() { 105 | Context context = BaseApplication.context; 106 | if (null == context) { 107 | return 0; 108 | } 109 | DisplayMetrics dm = null; 110 | try { 111 | if (context != null) { 112 | dm = new DisplayMetrics(); 113 | dm = context.getApplicationContext().getResources() 114 | .getDisplayMetrics(); 115 | } 116 | 117 | return dm.heightPixels; 118 | 119 | } catch (Exception e) { 120 | e.printStackTrace(); 121 | } 122 | return 0; 123 | } 124 | 125 | /** 126 | * 获取状态栏高度 127 | * 128 | * @param activity 129 | * @return 130 | */ 131 | public static int getStatusHeight(Activity activity) { 132 | int statusHeight = 0; 133 | Rect localRect = new Rect(); 134 | activity.getWindow().getDecorView() 135 | .getWindowVisibleDisplayFrame(localRect); 136 | statusHeight = localRect.top; 137 | if (0 == statusHeight) { 138 | Class localClass; 139 | try { 140 | localClass = Class.forName("com.android.internal.R$dimen"); 141 | Object localObject = localClass.newInstance(); 142 | int i5 = Integer.parseInt(localClass 143 | .getField("status_bar_height").get(localObject) 144 | .toString()); 145 | statusHeight = activity.getResources() 146 | .getDimensionPixelSize(i5); 147 | } catch (Exception e) { 148 | e.printStackTrace(); 149 | } 150 | } 151 | return statusHeight; 152 | } 153 | 154 | /** 155 | * 状态栏在darkMode下,字体为黑色,小米手机MIUI6以上有效 156 | * 157 | * @param darkmode 158 | * @param activity 159 | */ 160 | public static void setStatusBarDarkMode(boolean darkmode, Activity activity) { 161 | Class clazz = activity.getWindow().getClass(); 162 | try { 163 | int darkModeFlag = 0; 164 | Class layoutParams = Class 165 | .forName("android.view.MiuiWindowManager$LayoutParams"); 166 | Field field = layoutParams 167 | .getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); 168 | darkModeFlag = field.getInt(layoutParams); 169 | Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, 170 | int.class); 171 | extraFlagField.invoke(activity.getWindow(), darkmode ? darkModeFlag 172 | : 0, darkModeFlag); 173 | } catch (Exception e) { 174 | e.printStackTrace(); 175 | } 176 | } 177 | 178 | /** 179 | * 设置状态栏在darkMode下,字体的颜色,争对魅族手机 180 | */ 181 | public static boolean setStatusBarDarkIcon(Window window, boolean dark) { 182 | boolean result = false; 183 | if (window != null) { 184 | try { 185 | WindowManager.LayoutParams lp = window.getAttributes(); 186 | Field darkFlag = WindowManager.LayoutParams.class 187 | .getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON"); 188 | Field meizuFlags = WindowManager.LayoutParams.class 189 | .getDeclaredField("meizuFlags"); 190 | darkFlag.setAccessible(true); 191 | meizuFlags.setAccessible(true); 192 | int bit = darkFlag.getInt(null); 193 | int value = meizuFlags.getInt(lp); 194 | if (dark) { 195 | value |= bit; 196 | } else { 197 | value &= ~bit; 198 | } 199 | meizuFlags.setInt(lp, value); 200 | window.setAttributes(lp); 201 | result = true; 202 | } catch (Exception e) { 203 | Log.e("MeiZu", "setStatusBarDarkIcon: failed"); 204 | } 205 | } 206 | return result; 207 | } 208 | 209 | /** 210 | * 判断底部navigator是否已经显示 211 | */ 212 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) 213 | public static boolean hasSoftKeys(Activity context) { 214 | Display d = context.getWindowManager().getDefaultDisplay(); 215 | 216 | DisplayMetrics realDisplayMetrics = new DisplayMetrics(); 217 | d.getRealMetrics(realDisplayMetrics); 218 | 219 | int realHeight = realDisplayMetrics.heightPixels; 220 | int realWidth = realDisplayMetrics.widthPixels; 221 | 222 | DisplayMetrics displayMetrics = new DisplayMetrics(); 223 | d.getMetrics(displayMetrics); 224 | 225 | int displayHeight = displayMetrics.heightPixels; 226 | int displayWidth = displayMetrics.widthPixels; 227 | 228 | return (realWidth - displayWidth) > 0 229 | || (realHeight - displayHeight) > 0; 230 | } 231 | 232 | /** 233 | * 检查设备是否有虚拟导航栏 234 | * 235 | * @param context 236 | * @return 237 | */ 238 | public static boolean checkDeviceHasNavigationBar(Context context) { 239 | boolean hasNavigationBar = false; 240 | Resources rs = context.getResources(); 241 | int id = rs 242 | .getIdentifier("config_showNavigationBar", "bool", "android"); 243 | if (id > 0) { 244 | hasNavigationBar = rs.getBoolean(id); 245 | } 246 | try { 247 | Class systemPropertiesClass = Class 248 | .forName("android.os.SystemProperties"); 249 | Method m = systemPropertiesClass.getMethod("get", String.class); 250 | String navBarOverride = (String) m.invoke(systemPropertiesClass, 251 | "qemu.hw.mainkeys"); 252 | if ("1".equals(navBarOverride)) { 253 | hasNavigationBar = false; 254 | } else if ("0".equals(navBarOverride)) { 255 | hasNavigationBar = true; 256 | } 257 | } catch (Exception e) { 258 | Log.e(DisplayUtils.class.getSimpleName(), e.toString()); 259 | } 260 | 261 | return hasNavigationBar; 262 | 263 | } 264 | 265 | /** 266 | * 获取NavigationBar的高度 267 | * 268 | * @param context 269 | * @return 270 | */ 271 | public static int getNavigationBarHeight(Context context) { 272 | int navigationBarHeight = 0; 273 | Resources rs = context.getResources(); 274 | int id = rs.getIdentifier("navigation_bar_height", "dimen", "android"); 275 | if (id > 0 && checkDeviceHasNavigationBar(context)) { 276 | navigationBarHeight = rs.getDimensionPixelSize(id); 277 | } 278 | return navigationBarHeight; 279 | } 280 | 281 | } 282 | -------------------------------------------------------------------------------- /app/src/main/java/com/cc/library/view/util/ToastUtil.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.cc.library.view.util; 5 | 6 | import android.content.Context; 7 | import android.widget.Toast; 8 | 9 | import com.cc.library.view.base.BaseApplication; 10 | 11 | 12 | public class ToastUtil { 13 | 14 | public static void show(Context context, String info) { 15 | Toast.makeText(context, info, Toast.LENGTH_LONG).show(); 16 | } 17 | 18 | public static void show(Context context, int info) { 19 | Toast.makeText(context, info, Toast.LENGTH_LONG).show(); 20 | } 21 | 22 | private static Toast toast; 23 | private static Context context = BaseApplication.context; 24 | 25 | /** 26 | * 显示吐司 后出现的吐司立即取代之前的 27 | * 28 | * @param msg 消息 29 | * @param time 时间 0为短时间 否则为长时间 30 | */ 31 | public static void showToast(String msg, int time) { 32 | if (null != toast) 33 | toast.cancel(); 34 | 35 | if (time == 0) 36 | toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT); 37 | else 38 | toast = Toast.makeText(context, msg, Toast.LENGTH_LONG); 39 | 40 | toast.show(); 41 | } 42 | 43 | /** 44 | * 显示吐司 后出现的吐司立即取代之前的 45 | * 46 | * @param msg 消息字符资源 47 | * @param time 时间 0为短时间 否则为长时间 48 | */ 49 | public static void showToast(int msg, int time) { 50 | if (null != toast) 51 | toast.cancel(); 52 | 53 | if (time == 0) 54 | toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT); 55 | else 56 | toast = Toast.makeText(context, msg, Toast.LENGTH_LONG); 57 | 58 | toast.show(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/cc/library/view/widget/MFooterView.java: -------------------------------------------------------------------------------- 1 | package com.cc.library.view.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.ProgressBar; 6 | import android.widget.RelativeLayout; 7 | import android.widget.TextView; 8 | 9 | import com.cc.library.view.R; 10 | import com.library.widget.baseview.FooterView; 11 | 12 | /** 13 | * Created by zhangyu on 2016/11/18. 14 | */ 15 | 16 | public class MFooterView extends FooterView { 17 | 18 | private TextView tv; 19 | private ProgressBar progressBar; 20 | 21 | public MFooterView(Context context) { 22 | super(context); 23 | } 24 | 25 | public MFooterView(Context context, AttributeSet attrs) { 26 | super(context, attrs); 27 | } 28 | 29 | public MFooterView(Context context, AttributeSet attrs, int defStyleAttr) { 30 | super(context, attrs, defStyleAttr); 31 | } 32 | 33 | @Override 34 | public int getLayoutView() { 35 | return R.layout.m_footer_view; 36 | } 37 | 38 | @Override 39 | public void initView() { 40 | tv = (TextView) findViewById(R.id.mfv_tv); 41 | progressBar = (ProgressBar) findViewById(R.id.mfv_progress_bar); 42 | } 43 | 44 | @Override 45 | public RelativeLayout getRootContainer() { 46 | 47 | return (RelativeLayout) findViewById(R.id.mfv_container); 48 | } 49 | 50 | @Override 51 | public void setLoadingViewState() { 52 | progressBar.setVisibility(VISIBLE); 53 | tv.setText("正在加载..\n(我是自定义FooterView)"); 54 | } 55 | 56 | @Override 57 | public void setPullingViewState() { 58 | progressBar.setVisibility(INVISIBLE); 59 | tv.setText("上拉加载更多\n(我是自定义FooterView)"); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/cc/library/view/widget/MHeaderView.java: -------------------------------------------------------------------------------- 1 | package com.cc.library.view.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.ProgressBar; 6 | import android.widget.RelativeLayout; 7 | import android.widget.TextView; 8 | 9 | import com.cc.library.view.R; 10 | import com.library.widget.baseview.HeaderView; 11 | 12 | /** 13 | * Created by zhangyu on 2016/11/18. 14 | */ 15 | 16 | public class MHeaderView extends HeaderView { 17 | private TextView tv; 18 | private ProgressBar progressBar; 19 | public MHeaderView(Context context) { 20 | super(context); 21 | } 22 | 23 | public MHeaderView(Context context, AttributeSet attrs) { 24 | super(context, attrs); 25 | } 26 | 27 | public MHeaderView(Context context, AttributeSet attrs, int defStyleAttr) { 28 | super(context, attrs, defStyleAttr); 29 | } 30 | 31 | @Override 32 | public void initView() { 33 | tv = (TextView) findViewById(R.id.mhv_tv); 34 | progressBar = (ProgressBar) findViewById(R.id.mhv_progress_bar); 35 | } 36 | 37 | @Override 38 | public RelativeLayout getRootContainer() { 39 | 40 | return (RelativeLayout) findViewById(R.id.mhv_container); 41 | } 42 | 43 | @Override 44 | public int getLayoutView() { 45 | return R.layout.m_header_view; 46 | } 47 | 48 | @Override 49 | public void setRefreshingViewState() { 50 | progressBar.setVisibility(VISIBLE); 51 | tv.setText("正在刷新..\n(我是自定义HeaderView)"); 52 | } 53 | 54 | @Override 55 | public void setPullingViewState() { 56 | progressBar.setVisibility(INVISIBLE); 57 | tv.setText("下拉刷新\n(我是自定义HeaderView)"); 58 | } 59 | 60 | @Override 61 | public void setReadyViewState() { 62 | progressBar.setVisibility(INVISIBLE); 63 | tv.setText("放开刷新\n(我是自定义HeaderView)"); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_swipe_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_recyclerview_menu_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 16 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_view_for_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/m_footer_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/m_header_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/menu1_for_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/menu_for_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/menu/prt_act_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyuChen1991/PtrSwipeMenuRecyclerView/1d246ddf5c102e6b0cb9555cda4a3c27f79cbce8/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyuChen1991/PtrSwipeMenuRecyclerView/1d246ddf5c102e6b0cb9555cda4a3c27f79cbce8/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyuChen1991/PtrSwipeMenuRecyclerView/1d246ddf5c102e6b0cb9555cda4a3c27f79cbce8/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyuChen1991/PtrSwipeMenuRecyclerView/1d246ddf5c102e6b0cb9555cda4a3c27f79cbce8/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyuChen1991/PtrSwipeMenuRecyclerView/1d246ddf5c102e6b0cb9555cda4a3c27f79cbce8/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | #ffffff 8 | 9 | #0099CC 10 | 11 | #00000000 12 | 13 | #FFFFF0 14 | 15 | #FFFFE0 16 | 17 | #FFFF00 18 | 19 | #FFFAFA 20 | 21 | #FFFAF0 22 | 23 | #FFFACD 24 | 25 | #FFF8DC 26 | 27 | #FFF5EE 28 | 29 | #FFF0F5 30 | 31 | #FFEFD5 32 | 33 | #FFEBCD 34 | 35 | #FFE4E1 36 | 37 | #FFE4C4 38 | 39 | #FFE4B5 40 | 41 | #FFDEAD 42 | 43 | #FFDAB9 44 | 45 | #FFD700 46 | 47 | #FFC0CB 48 | 49 | #FFB6C1 50 | 51 | #FFA500 52 | 53 | #FFA07A 54 | 55 | #FF8C00 56 | 57 | #FF7F50 58 | 59 | #FF69B4 60 | 61 | #FF6347 62 | 63 | #FF4500 64 | 65 | #FF1493 66 | 67 | #FF00FF 68 | 69 | #FF00FF 70 | 71 | #FF0000 72 | 73 | #FDF5E6 74 | 75 | #FAFAD2 76 | 77 | #FAF0E6 78 | 79 | #FAEBD7 80 | 81 | #FA8072 82 | 83 | #F8F8FF 84 | 85 | #F5FFFA 86 | 87 | #F5F5F5 88 | 89 | #F5F5DC 90 | 91 | #F5DEB3 92 | 93 | #F4A460 94 | 95 | #F0FFFF 96 | 97 | #F0FFF0 98 | 99 | #F0F8FF 100 | 101 | #F0E68C 102 | 103 | #F08080 104 | 105 | #EEE8AA 106 | 107 | #EE82EE 108 | 109 | #E9967A 110 | 111 | #E6E6FA 112 | 113 | #E0FFFF 114 | 115 | #DEB887 116 | 117 | #DDA0DD 118 | 119 | #DCDCDC 120 | 121 | #DC143C 122 | 123 | #DB7093 124 | 125 | #DAA520 126 | 127 | #DA70D6 128 | 129 | #D8BFD8 130 | 131 | #D3D3D3 132 | 133 | #D3D3D3 134 | 135 | #D2B48C 136 | 137 | #D2691E 138 | 139 | #CD853F 140 | 141 | #CD5C5C 142 | 143 | #C71585 144 | 145 | #C0C0C0 146 | 147 | #BDB76B 148 | 149 | #BC8F8F 150 | 151 | #BA55D3 152 | 153 | #B8860B 154 | 155 | #B22222 156 | 157 | #B0E0E6 158 | 159 | #B0C4DE 160 | 161 | #AFEEEE 162 | 163 | #ADFF2F 164 | 165 | #ADD8E6 166 | 167 | #A9A9A9 168 | 169 | #A9A9A9 170 | 171 | #A52A2A 172 | 173 | #A0522D 174 | 175 | #9932CC 176 | 177 | #98FB98 178 | 179 | #9400D3 180 | 181 | #9370DB 182 | 183 | #90EE90 184 | 185 | #8FBC8F 186 | 187 | #8B4513 188 | 189 | #8B008B 190 | 191 | #8B0000 192 | 193 | #8A2BE2 194 | 195 | #87CEFA 196 | 197 | #87CEEB 198 | 199 | #808080 200 | 201 | #808080 202 | 203 | #808000 204 | 205 | #800080 206 | 207 | #800000 208 | 209 | #7FFFD4 210 | 211 | #7FFF00 212 | 213 | #7CFC00 214 | 215 | #7B68EE 216 | 217 | #778899 218 | 219 | #778899 220 | 221 | #708090 222 | 223 | #708090 224 | 225 | #6B8E23 226 | 227 | #6A5ACD 228 | 229 | #696969 230 | 231 | #696969 232 | 233 | #66CDAA 234 | 235 | #6495ED 236 | 237 | #5F9EA0 238 | 239 | #556B2F 240 | 241 | #4B0082 242 | 243 | #48D1CC 244 | 245 | #483D8B 246 | 247 | #4682B4 248 | 249 | #4169E1 250 | 251 | #40E0D0 252 | 253 | #3CB371 254 | 255 | #32CD32 256 | 257 | #2F4F4F 258 | 259 | #2F4F4F 260 | 261 | #2E8B57 262 | 263 | #228B22 264 | 265 | #20B2AA 266 | 267 | #1E90FF 268 | 269 | #191970 270 | 271 | #00FFFF 272 | 273 | #00FFFF 274 | 275 | #00FF7F 276 | 277 | #00FF00 278 | 279 | #00FA9A 280 | 281 | #00CED1 282 | 283 | #8700BFFF 284 | #00BFFF 285 | 286 | #008B8B 287 | 288 | #008080 289 | 290 | #008000 291 | 292 | #006400 293 | 294 | #0000FF 295 | 296 | #0000CD 297 | 298 | #00008B 299 | 300 | #000080 301 | 302 | #000000 303 | 304 | 305 | #c0ffbd21 306 | #b0000000 307 | #ffffffff 308 | #ffcc0000 309 | #60000000 310 | 311 | #99ffffff 312 | #00ffffff 313 | 314 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | PtrSwipeMenuRecyclerView 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.2' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyuChen1991/PtrSwipeMenuRecyclerView/1d246ddf5c102e6b0cb9555cda4a3c27f79cbce8/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 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-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 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 Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /images/loadmore.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyuChen1991/PtrSwipeMenuRecyclerView/1d246ddf5c102e6b0cb9555cda4a3c27f79cbce8/images/loadmore.jpg -------------------------------------------------------------------------------- /images/refreshing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyuChen1991/PtrSwipeMenuRecyclerView/1d246ddf5c102e6b0cb9555cda4a3c27f79cbce8/images/refreshing.jpg -------------------------------------------------------------------------------- /images/swipemenu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangyuChen1991/PtrSwipeMenuRecyclerView/1d246ddf5c102e6b0cb9555cda4a3c27f79cbce8/images/swipemenu.jpg -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.0" 6 | 7 | defaultConfig { 8 | minSdkVersion 15 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | compile fileTree(include: ['*.jar'], dir: 'libs') 26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 27 | exclude group: 'com.android.support', module: 'support-annotations' 28 | }) 29 | compile 'com.android.support:appcompat-v7:25.0.0' 30 | testCompile 'junit:junit:4.12' 31 | compile 'com.android.support:recyclerview-v7:25.0.0' 32 | } 33 | -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\czyWorkSpace\envirment\android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/src/main/java/com/library/widget/adapter/SwipeMenuAdapter.java: -------------------------------------------------------------------------------- 1 | package com.library.widget.adapter; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.util.Log; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.LinearLayout; 8 | 9 | import com.library.widget.defaultimple.DefaultFooterView; 10 | import com.library.widget.defaultimple.DefaultHeaderView; 11 | import com.library.widget.baseview.FooterView; 12 | import com.library.widget.baseview.HeaderView; 13 | import com.library.widget.baseview.PtrSwipeMenuRecyclerView; 14 | import com.library.widget.baseview.SwipeMenuLayout; 15 | 16 | /** 17 | * Created by zhangyu on 2016/11/9. 18 | */ 19 | public abstract class SwipeMenuAdapter extends RecyclerView.Adapter { 20 | private static final String TAG = "SwipeMenuAdapter"; 21 | private LinearLayout menuView; 22 | private View contentView; 23 | 24 | 25 | private HeaderView headerView; 26 | 27 | private FooterView footerView; 28 | public static final int HeaderType = 0x1099, FooterType = 0x1101; 29 | private HeaderFooterViewHolder headerViewHolder, footerViewHolder; 30 | private boolean footerViewEnable = true; 31 | 32 | public class HeaderFooterViewHolder extends RecyclerView.ViewHolder { 33 | public HeaderFooterViewHolder(View itemView) { 34 | super(itemView); 35 | } 36 | } 37 | 38 | @Override 39 | public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 40 | Log.d(TAG, "onCreateViewHolder.. viewType = " + viewType + " HT:" + HeaderType + " FT:" + FooterType); 41 | if (viewType == HeaderType) { 42 | headerView = headerView == null ? new DefaultHeaderView(parent.getContext()) : headerView; 43 | headerViewHolder = new HeaderFooterViewHolder(headerView); 44 | return headerViewHolder; 45 | } 46 | if (viewType == FooterType) { 47 | footerView = footerView == null ? new DefaultFooterView(parent.getContext()) : footerView; 48 | footerViewHolder = new HeaderFooterViewHolder(footerView); 49 | if(!footerViewEnable) { //不允许上拉加载更多,隐藏FooterView 50 | FooterView footerView = (FooterView) footerViewHolder.itemView; 51 | footerView.setNowState(FooterView.STATE.HIND); 52 | } 53 | return footerViewHolder; 54 | } 55 | 56 | menuView = createMenuView(parent, viewType); 57 | contentView = createContentView(parent, viewType); 58 | SwipeMenuLayout swipeMenuLayout = new SwipeMenuLayout(parent.getContext(), contentView, menuView); 59 | return onCreateThisViewHolder(swipeMenuLayout, viewType); 60 | } 61 | 62 | /** 63 | * 创建item内容的view布局 64 | * 65 | * @param parent 66 | * @param viewType 67 | * @return 68 | */ 69 | protected abstract View createContentView(ViewGroup parent, int viewType); 70 | 71 | /** 72 | * 创建菜单view的布局(高度最好与contentView布局高度一致,否则可能引起布局混乱) 73 | * 如果不需要侧滑菜单,返回null 74 | * 75 | * @return 76 | */ 77 | protected abstract LinearLayout createMenuView(ViewGroup parent, int viewType); 78 | 79 | /** 80 | * 创建ViewHolder 81 | * 82 | * @param contentView 已经在createContentView()中创建好,然后经过再次包裹了侧滑菜单布局的itemview 83 | * @param viewType 84 | * @return 85 | */ 86 | public abstract RecyclerView.ViewHolder onCreateThisViewHolder(ViewGroup contentView, int viewType); 87 | 88 | @Override 89 | public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 90 | if (position != 0 && position != getItemCount() - 1) 91 | onBindThisViewHolder((V) holder, position - 1); 92 | } 93 | 94 | public abstract void onBindThisViewHolder(V holder, int position); 95 | 96 | @Override 97 | public final int getItemCount() { 98 | //添加Header和Footer的数目 99 | return getThisItemCount() + 2; 100 | } 101 | 102 | /** 103 | * 此方法执行RecyclerView.Adapter中getItemCount()的逻辑 104 | * 105 | * @return 106 | */ 107 | public abstract int getThisItemCount(); 108 | 109 | /** 110 | * 重写此方法时请注意保留父类方法的逻辑,否则导致header计数混乱,下拉刷新出错 111 | * 使用position时注意减1(减去header的位置) 112 | * 113 | * @param position 114 | * @return 115 | */ 116 | @Override 117 | public final int getItemViewType(int position) { 118 | if (position == 0) 119 | return HeaderType; 120 | else if (position == getThisItemCount() + 1) 121 | return FooterType; 122 | else 123 | return getThisItemViewType(position - 1);//减1减去header位置 124 | } 125 | 126 | /** 127 | * 此方法替代原Adapter中的getItemViewType方法 128 | * 使用与 129 | * @return 130 | */ 131 | public int getThisItemViewType(int position) { 132 | return 0; 133 | } 134 | 135 | 136 | /** 137 | * 获取HeaderView 138 | * 139 | * @return 140 | */ 141 | public HeaderView getHeaderView() { 142 | return (HeaderView) headerViewHolder.itemView; 143 | } 144 | 145 | /** 146 | * 获取FooterView 147 | * 148 | * @return 149 | */ 150 | public FooterView getFooterView() { 151 | if (null == footerViewHolder) 152 | return null; 153 | return (FooterView) footerViewHolder.itemView; 154 | } 155 | 156 | /** 157 | * 设置HeaderView 158 | * 159 | * @return 160 | */ 161 | public void setHeaderView(HeaderView headerView) { 162 | this.headerView = headerView; 163 | notifyDataSetChanged(); 164 | } 165 | 166 | /** 167 | * 设置FooterView 168 | * 169 | * @return 170 | */ 171 | public void setFooterView(FooterView footerView) { 172 | this.footerView = footerView; 173 | } 174 | 175 | public void setFooterViewEnable(boolean enable) { 176 | this.footerViewEnable = enable; 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /library/src/main/java/com/library/widget/baseview/FooterView.java: -------------------------------------------------------------------------------- 1 | package com.library.widget.baseview; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.widget.RelativeLayout; 7 | 8 | /** 9 | * Created by zhangyu on 2016/11/18. 10 | */ 11 | 12 | public abstract class FooterView extends RelativeLayout{ 13 | public RelativeLayout rootContainer; 14 | private STATE nowState; 15 | public enum STATE{ 16 | LOADING, 17 | READY, 18 | HIND 19 | } 20 | 21 | public FooterView(Context context) { 22 | super(context); 23 | init(context,null); 24 | } 25 | 26 | public FooterView(Context context, AttributeSet attrs) { 27 | super(context, attrs); 28 | init(context,attrs); 29 | } 30 | 31 | 32 | public FooterView(Context context, AttributeSet attrs, int defStyleAttr) { 33 | super(context, attrs, defStyleAttr); 34 | init(context,attrs); 35 | } 36 | 37 | 38 | private void init(Context context, AttributeSet attrs) { 39 | View.inflate(context, getLayoutView(),this); 40 | rootContainer = getRootContainer(); 41 | initView(); 42 | } 43 | 44 | /** 45 | * 指定自定义的布局 46 | * @return 47 | */ 48 | public abstract int getLayoutView(); 49 | 50 | /** 51 | * 初始化布局 52 | */ 53 | public abstract void initView(); 54 | 55 | /** 56 | * 获取自定义布局的根布局容器 57 | * @return 58 | */ 59 | public abstract RelativeLayout getRootContainer(); 60 | 61 | /** 62 | * 设置正在加载时的view状态 63 | */ 64 | public abstract void setLoadingViewState(); 65 | 66 | /** 67 | * 设置上拉时的view状态 68 | */ 69 | public abstract void setPullingViewState(); 70 | 71 | public void setNowState(FooterView.STATE nowState) { 72 | this.nowState = nowState; 73 | refreshView(); 74 | } 75 | 76 | private void refreshView() { 77 | switch (nowState){ 78 | case LOADING: 79 | rootContainer.setVisibility(View.VISIBLE); 80 | setLoadingViewState(); 81 | break; 82 | case READY: 83 | rootContainer.setVisibility(View.VISIBLE); 84 | setPullingViewState(); 85 | break; 86 | case HIND: 87 | rootContainer.setVisibility(View.GONE); 88 | break; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /library/src/main/java/com/library/widget/baseview/HeaderView.java: -------------------------------------------------------------------------------- 1 | package com.library.widget.baseview; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.widget.RelativeLayout; 7 | 8 | /** 9 | * Created by zhangyu on 2016/11/18. 10 | */ 11 | 12 | public abstract class HeaderView extends RelativeLayout { 13 | 14 | public RelativeLayout rootContainer; 15 | protected STATE nowState; 16 | public enum STATE { 17 | PULLING, 18 | READY, 19 | REFRESHING, 20 | HIND 21 | } 22 | 23 | public HeaderView(Context context) { 24 | super(context); 25 | init(context,null); 26 | } 27 | 28 | public HeaderView(Context context, AttributeSet attrs) { 29 | super(context, attrs); 30 | init(context, attrs); 31 | } 32 | 33 | 34 | public HeaderView(Context context, AttributeSet attrs, int defStyleAttr) { 35 | super(context, attrs, defStyleAttr); 36 | init(context,attrs); 37 | } 38 | 39 | 40 | private void init(Context context, AttributeSet attrs) { 41 | View.inflate(context, getLayoutView(), this); 42 | rootContainer = getRootContainer(); 43 | initView(); 44 | setNowState(HeaderView.STATE.HIND); 45 | } 46 | 47 | 48 | /** 49 | * 设置HeaderView的高度 50 | * @param height 51 | */ 52 | public void setViewHeight(float height) { 53 | RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) rootContainer.getLayoutParams(); 54 | params.height = (int) height; 55 | rootContainer.setLayoutParams(params); 56 | invalidate(); 57 | } 58 | 59 | /** 60 | * 初始化布局 61 | */ 62 | public abstract void initView(); 63 | 64 | /** 65 | * 获取自定义布局的根布局容器 66 | * @return 67 | */ 68 | public abstract RelativeLayout getRootContainer(); 69 | 70 | /** 71 | * 指定自定义的布局 72 | * 73 | * @return 74 | */ 75 | public abstract int getLayoutView(); 76 | 77 | /** 78 | * 设置正在加载时的view状态 79 | */ 80 | public abstract void setRefreshingViewState(); 81 | 82 | /** 83 | * 设置下拉时的view状态 84 | */ 85 | public abstract void setPullingViewState(); 86 | 87 | /** 88 | * 设置准备好刷新时状态 89 | */ 90 | public abstract void setReadyViewState(); 91 | 92 | public void setNowState(HeaderView.STATE nowState) { 93 | this.nowState = nowState; 94 | refreshView(); 95 | } 96 | 97 | private void refreshView() { 98 | 99 | switch (nowState) { 100 | case PULLING: 101 | rootContainer.setVisibility(View.VISIBLE); 102 | setPullingViewState(); 103 | break; 104 | case READY: 105 | rootContainer.setVisibility(View.VISIBLE); 106 | setReadyViewState(); 107 | break; 108 | case REFRESHING: 109 | rootContainer.setVisibility(View.VISIBLE); 110 | setRefreshingViewState(); 111 | break; 112 | case HIND: 113 | rootContainer.setVisibility(View.GONE); 114 | break; 115 | } 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /library/src/main/java/com/library/widget/baseview/PtrSwipeMenuRecyclerView.java: -------------------------------------------------------------------------------- 1 | package com.library.widget.baseview; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ObjectAnimator; 5 | import android.animation.ValueAnimator; 6 | import android.content.Context; 7 | import android.support.annotation.Nullable; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.util.AttributeSet; 10 | import android.util.Log; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | import android.widget.LinearLayout; 14 | 15 | import com.library.widget.adapter.SwipeMenuAdapter; 16 | import com.library.widget.interfaces.OnMenuClickListener; 17 | 18 | import java.io.InvalidClassException; 19 | 20 | /** 21 | * Created by zhangyu on 2016/11/7. 22 | */ 23 | public class PtrSwipeMenuRecyclerView extends RecyclerView { 24 | private static final String TAG = "PSMRecyclerView"; 25 | private boolean isVerticalScroll = true; 26 | private Context context; 27 | private SwipeMenuAdapter adapter; 28 | //最新的touch Y坐标点 29 | private float touchCurrY; 30 | //是否正在刷新、是否正在加载更多 31 | private boolean refreshing = false, loading = false; 32 | //触发刷新的临界值 33 | private final float refreshHeight = 150f; 34 | //HeaderView最新的高度 35 | private int currHeaderHeight; 36 | //是否允许上拉加载更多 37 | private boolean pullLoadMoreEnable = true; 38 | //是否允许下拉刷新 39 | private boolean pullToRefreshEnable = true; 40 | private OnPullListener onPullListener; 41 | 42 | private static OnMenuClickListener onMenuClickListener; 43 | 44 | public PtrSwipeMenuRecyclerView(Context context) { 45 | super(context); 46 | init(context); 47 | } 48 | 49 | public PtrSwipeMenuRecyclerView(Context context, @Nullable AttributeSet attrs) { 50 | super(context, attrs); 51 | init(context); 52 | } 53 | 54 | public PtrSwipeMenuRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { 55 | super(context, attrs, defStyle); 56 | init(context); 57 | } 58 | 59 | @Override 60 | public void setAdapter(Adapter adapter) { 61 | super.setAdapter(adapter); 62 | if (adapter instanceof SwipeMenuAdapter) { 63 | this.adapter = (SwipeMenuAdapter) adapter; 64 | } else { 65 | try { 66 | throw new InvalidClassException("所使用Adapter并非SwipeMenuAdapter的子类"); 67 | } catch (InvalidClassException e) { 68 | Log.e(TAG, e.getMessage()); 69 | e.printStackTrace(); 70 | } 71 | } 72 | } 73 | 74 | 75 | private void init(Context context) { 76 | this.context = context; 77 | 78 | 79 | } 80 | 81 | /** 82 | * 监听滚动 83 | * 84 | * @param dx 85 | * @param dy 86 | */ 87 | @Override 88 | public void onScrolled(int dx, int dy) { 89 | super.onScrolled(dx, dy); 90 | if (!canScrollVertically(1)) {//已经滑到底部 91 | 92 | if (loading || !pullLoadMoreEnable) 93 | super.onScrolled(dx, dy); 94 | else { 95 | startLoadMore();//自动加载 96 | } 97 | } 98 | } 99 | 100 | /** 101 | * 重写ViewHolder 102 | * 使用PtrSwipeMenuRecyclerView,必须配合PtrSwipeMenuRecyclerView.ViewHolder,否则将会出错 103 | */ 104 | public static abstract class ViewHolder extends RecyclerView.ViewHolder { 105 | SwipeMenuLayout swipeMenuLayout; 106 | LinearLayout menuView; 107 | 108 | public ViewHolder(View itemView) { 109 | super(itemView); 110 | swipeMenuLayout = (SwipeMenuLayout) itemView; 111 | menuView = swipeMenuLayout.getMenuView(); 112 | setOnMenuClickListener(); 113 | } 114 | 115 | private void setOnMenuClickListener() { 116 | if (null != menuView) 117 | for (int i = 0; i < menuView.getChildCount(); i++) { 118 | menuView.getChildAt(i).setOnClickListener(onClickListener); 119 | } 120 | 121 | } 122 | 123 | private View.OnClickListener onClickListener = new OnClickListener() { 124 | @Override 125 | public void onClick(View view) { 126 | for (int i = 0; i < menuView.getChildCount(); i++) { 127 | if (view.getId() == menuView.getChildAt(i).getId()) { 128 | swipeMenuLayout.smoothCloseMenu(); 129 | if (null != onMenuClickListener) { 130 | onMenuClickListener.onMenuClick(view, getAdapterPosition() - 1);//触发菜单点击事件,减去header的位置 131 | } 132 | } 133 | } 134 | } 135 | }; 136 | } 137 | 138 | boolean thisTouchHadDeal = false; 139 | private int startX, nowTouchX, startY, nowTouchY,oldTouchPosition = -1; 140 | private SwipeMenuLayout newSwipeMenuLayout, oldSwipeMenuLayout; 141 | 142 | @Override 143 | public boolean onInterceptTouchEvent(MotionEvent ev) { 144 | 145 | switch (ev.getAction()) { 146 | case MotionEvent.ACTION_DOWN: 147 | isVerticalScroll = true; 148 | thisTouchHadDeal = false; 149 | startX = (int) ev.getX(); 150 | startY = (int) ev.getY(); 151 | 152 | int touchingPosition = getChildAdapterPosition(findChildViewUnder(startX, startY)); 153 | if (touchingPosition != adapter.getItemCount() - 1 && touchingPosition != 0) { 154 | ViewHolder viewHolder = (ViewHolder) findViewHolderForAdapterPosition(touchingPosition); 155 | 156 | newSwipeMenuLayout = (SwipeMenuLayout) viewHolder.itemView; 157 | if (touchingPosition != oldTouchPosition && oldSwipeMenuLayout != null && oldSwipeMenuLayout.getScrollX() != 0)//上一次被弹出的菜单回滚回原位 158 | oldSwipeMenuLayout.smoothCloseMenu(); 159 | oldSwipeMenuLayout = newSwipeMenuLayout; 160 | } 161 | oldTouchPosition = touchingPosition; 162 | break; 163 | case MotionEvent.ACTION_MOVE: 164 | if (!thisTouchHadDeal) { 165 | thisTouchHadDeal = true; 166 | nowTouchX = (int) ev.getX(); 167 | nowTouchY = (int) ev.getY(); 168 | isVerticalScroll = !isHorizontalScroll(startX, startY, nowTouchX, nowTouchY); 169 | } 170 | if (isVerticalScroll) { //如果是竖直方向滑动,拦截事件自己处理 171 | return true; 172 | } else { //横向滑动,不做响应事件,让子View处理事件 173 | return false; 174 | } 175 | case MotionEvent.ACTION_UP: 176 | isVerticalScroll = true; 177 | thisTouchHadDeal = false; 178 | break; 179 | } 180 | 181 | return super.onInterceptTouchEvent(ev); 182 | } 183 | 184 | @Override 185 | public boolean onTouchEvent(MotionEvent e) { 186 | switch (e.getAction()) { 187 | case MotionEvent.ACTION_DOWN: 188 | break; 189 | case MotionEvent.ACTION_MOVE: 190 | 191 | Log.d(TAG, "computeVerticalScrollOffset() = " + computeVerticalScrollOffset()); 192 | //理论上应该computeVerticalScrollOffset == 0 但是因为隐藏header的缘故,这里的判断距离增加一个item的高度 193 | if (getChildAt(1) != null && computeVerticalScrollOffset() <= getChildAt(1).getHeight()) {//recyclerviwe已滑动到顶部 194 | if (refreshing || !pullToRefreshEnable) 195 | return super.onTouchEvent(e); 196 | touchCurrY = e.getY(); 197 | 198 | float distanceY = touchCurrY - startY; 199 | if (distanceY > 0) { 200 | dealPulling(distanceY); 201 | return true; 202 | } 203 | } 204 | 205 | // if (!canScrollVertically(1)) {//已经滑到底部 206 | // 207 | // if (loading || !pullLoadMoreEnable) 208 | // return super.onTouchEvent(e); 209 | // 210 | // float distanceY = touchCurrY - startY; 211 | // if (distanceY < 0) { 212 | // startLoadMore(); 213 | // return true; 214 | // } 215 | // } 216 | break; 217 | case MotionEvent.ACTION_UP: 218 | if (!refreshing) { 219 | currHeaderHeight = adapter.getHeaderView().getHeight(); 220 | dealPullUp(); 221 | } 222 | break; 223 | } 224 | return super.onTouchEvent(e); 225 | } 226 | 227 | /** 228 | * 开始加载更多 229 | */ 230 | private void startLoadMore() { 231 | loading = true; 232 | if (null != onPullListener) 233 | onPullListener.onLoadMore(); 234 | 235 | adapter.getFooterView().setNowState(FooterView.STATE.LOADING); 236 | } 237 | 238 | public void onLoadMoreComplete() { 239 | loading = false; 240 | adapter.getFooterView().setNowState(FooterView.STATE.READY); 241 | } 242 | 243 | private void dealPullUp() { 244 | HeaderView headerView = adapter.getHeaderView(); 245 | int height = headerView.getHeight(); 246 | if (height < refreshHeight) { 247 | startScrollBackAnim(); 248 | } else { 249 | scrollToReadyAndRefresh(); 250 | } 251 | } 252 | 253 | 254 | /** 255 | * 回滚到刷新状态的位置并开始刷新 256 | */ 257 | private void scrollToReadyAndRefresh() { 258 | if (null != onPullListener) 259 | onPullListener.onRefresh(); 260 | refreshing = true; 261 | ValueAnimator animator = ObjectAnimator.ofFloat(1, refreshHeight / currHeaderHeight); 262 | animator.setDuration(200); 263 | animator.addUpdateListener(scrollToReadyUpdateListener); 264 | animator.addListener(scrollToReadyAnimatorListener); 265 | animator.start(); 266 | } 267 | 268 | private ValueAnimator.AnimatorUpdateListener scrollToReadyUpdateListener = new ValueAnimator.AnimatorUpdateListener() { 269 | @Override 270 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 271 | float value = (float) valueAnimator.getAnimatedValue(); 272 | adapter.getHeaderView().setViewHeight(currHeaderHeight * value); 273 | } 274 | }; 275 | 276 | private Animator.AnimatorListener scrollToReadyAnimatorListener = new Animator.AnimatorListener() { 277 | @Override 278 | public void onAnimationStart(Animator animator) { 279 | 280 | } 281 | 282 | @Override 283 | public void onAnimationEnd(Animator animator) { 284 | adapter.getHeaderView().setNowState(HeaderView.STATE.REFRESHING); 285 | } 286 | 287 | @Override 288 | public void onAnimationCancel(Animator animator) { 289 | 290 | } 291 | 292 | @Override 293 | public void onAnimationRepeat(Animator animator) { 294 | 295 | } 296 | }; 297 | 298 | 299 | /** 300 | * 开启回滚回原位的动画 301 | */ 302 | private void startScrollBackAnim() { 303 | ValueAnimator animator = ObjectAnimator.ofFloat(1, 0); 304 | animator.setDuration(150); 305 | animator.addUpdateListener(scrollBackAnimatUpdateListener); 306 | animator.start(); 307 | } 308 | 309 | private ValueAnimator.AnimatorUpdateListener scrollBackAnimatUpdateListener = new ValueAnimator.AnimatorUpdateListener() { 310 | @Override 311 | public void onAnimationUpdate(ValueAnimator valueAnimator) { 312 | float value = (float) valueAnimator.getAnimatedValue(); 313 | adapter.getHeaderView().setViewHeight(currHeaderHeight * value); 314 | if (value == 0) { 315 | refreshing = false; 316 | } 317 | } 318 | }; 319 | 320 | /** 321 | * 处理下拉过程 322 | * 323 | * @param distanceY 手指下拉的距离 324 | */ 325 | private void dealPulling(float distanceY) { 326 | 327 | float moveDistance = distanceY * 0.6f; 328 | adapter.getHeaderView().setViewHeight(moveDistance); 329 | if (moveDistance < refreshHeight) { 330 | adapter.getHeaderView().setNowState(HeaderView.STATE.PULLING); 331 | } else { 332 | adapter.getHeaderView().setNowState(HeaderView.STATE.READY); 333 | } 334 | } 335 | 336 | /** 337 | * 结束下拉刷新 338 | */ 339 | public void onRefreshComplete() { 340 | currHeaderHeight = (int) refreshHeight; 341 | startScrollBackAnim(); 342 | } 343 | 344 | /** 345 | * 设置菜单点击监听 346 | * 347 | * @param onMenuClickListener 348 | */ 349 | public void setOnMenuClickListener(OnMenuClickListener onMenuClickListener) { 350 | this.onMenuClickListener = onMenuClickListener; 351 | } 352 | 353 | /** 354 | * 判断是横向滑动还是纵向滑动 355 | * 356 | * @param startX 357 | * @param startY 358 | * @param secondX 359 | * @param secondY 360 | * @return 361 | */ 362 | 363 | private boolean isHorizontalScroll(int startX, int startY, int secondX, int secondY) { 364 | boolean ret = false; 365 | int distanceX = Math.abs(startX - secondX); 366 | int distanceY = Math.abs(startY - secondY); 367 | 368 | if (distanceX * Math.tan(2 * Math.PI / 360 * 30) > distanceY)//水平30度角度内算作水平滑动 369 | ret = true; 370 | return ret; 371 | } 372 | 373 | /** 374 | * 设置下拉刷新和上拉加载的事件监听 375 | * 376 | * @param onPullListener 377 | */ 378 | public void setOnPullListener(OnPullListener onPullListener) { 379 | this.onPullListener = onPullListener; 380 | } 381 | 382 | /** 383 | * 设置是否允许上拉加载更多 384 | * 385 | * @param enable true 允许上拉加载更多;false 不允许上拉加载更多 386 | */ 387 | public void setPullLoadMoreEnable(boolean enable) { 388 | this.pullLoadMoreEnable = enable; 389 | 390 | if (null != adapter && !enable) 391 | adapter.setFooterViewEnable(false); 392 | } 393 | 394 | /** 395 | * 设置是否允许下拉刷新 396 | * 397 | * @param enable true 允许下拉刷新;false 不允许下拉刷新 398 | */ 399 | public void setPullToRefreshEnable(boolean enable) { 400 | this.pullToRefreshEnable = enable; 401 | } 402 | 403 | public interface OnPullListener { 404 | /** 405 | * 下拉刷新触发执行方法 406 | */ 407 | void onRefresh(); 408 | 409 | /** 410 | * 加载更多触发执行方法 411 | */ 412 | void onLoadMore(); 413 | } 414 | 415 | } 416 | -------------------------------------------------------------------------------- /library/src/main/java/com/library/widget/baseview/SwipeMenuLayout.java: -------------------------------------------------------------------------------- 1 | package com.library.widget.baseview; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.util.Log; 6 | import android.view.MotionEvent; 7 | import android.view.View; 8 | import android.view.animation.Interpolator; 9 | import android.widget.FrameLayout; 10 | import android.widget.LinearLayout; 11 | import android.widget.Scroller; 12 | 13 | /** 14 | * Created by zhangyu on 2016/11/8. 15 | */ 16 | public class SwipeMenuLayout extends FrameLayout { 17 | private static final String TAG = "SwipeMenuLayout"; 18 | 19 | private View contentView; 20 | private LinearLayout menuView; 21 | private Context context; 22 | private Scroller mScroller; 23 | 24 | public SwipeMenuLayout(Context context, AttributeSet attrs) { 25 | super(context, attrs); 26 | init(context, attrs); 27 | } 28 | 29 | public SwipeMenuLayout(Context context) { 30 | super(context); 31 | init(context, null); 32 | } 33 | 34 | public SwipeMenuLayout(Context context, AttributeSet attrs, int defStyleAttr) { 35 | super(context, attrs, defStyleAttr); 36 | init(context, attrs); 37 | } 38 | 39 | public SwipeMenuLayout(Context context, View contentView, LinearLayout menuView) { 40 | super(context); 41 | this.contentView = contentView; 42 | this.menuView = menuView; 43 | init(context, null); 44 | } 45 | 46 | private void init(Context context, AttributeSet attrs) { 47 | this.context = context; 48 | mScroller = new Scroller(context); 49 | 50 | 51 | LayoutParams contentParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 52 | if (null != contentView) { 53 | addView(contentView); 54 | contentView.setLayoutParams(contentParams); 55 | } 56 | if (null != menuView) { 57 | addView(menuView); 58 | menuView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); 59 | } 60 | setLayoutParams(contentParams); 61 | 62 | 63 | } 64 | 65 | private float preTouchX, preTouchY; 66 | private float nowTouchX, nowTouchY; 67 | 68 | 69 | @Override 70 | public boolean onTouchEvent(MotionEvent event) { 71 | switch (event.getAction()) { 72 | case MotionEvent.ACTION_DOWN: 73 | //点击停止滚动,或者开始下一轮滚动的数据设置 74 | if (!mScroller.computeScrollOffset()) { // 滚动已经结束 75 | 76 | Log.i(TAG, "ACTION_DOWN.."); 77 | preTouchX = event.getX(); 78 | preTouchY = event.getY(); 79 | return true; 80 | } 81 | break; 82 | case MotionEvent.ACTION_MOVE: 83 | if (!mScroller.computeScrollOffset()) { // 滚动已结束 84 | nowTouchX = event.getX(); 85 | nowTouchY = event.getY(); 86 | 87 | if (null == menuView) 88 | return super.onTouchEvent(event); 89 | 90 | //当从0点往左滑动时,currx为整数,滑得越远,数越大 91 | int currX = mScroller.getCurrX(); 92 | float distanceX = nowTouchX - preTouchX; 93 | if (currX < menuView.getWidth() && currX > 0) {//菜单已被拉出,往左右都可滑 94 | if (distanceX < 0) {//往左拖,打开菜单方向 95 | //防止滑动距离超过菜单宽度 96 | distanceX = (-distanceX + currX > menuView.getWidth()) ? currX - menuView.getWidth() : distanceX; 97 | } 98 | 99 | if (distanceX > 0) {//往右拖,关闭菜单方向 100 | distanceX = distanceX > currX ? currX : distanceX; 101 | } 102 | return doScroll(distanceX); 103 | } 104 | 105 | //菜单已被完全拉出,往右可滑 106 | if (currX >= menuView.getWidth() && distanceX > 0) { 107 | Log.i(TAG, "currX = " + currX + " ,distanceX = " + distanceX + " ,menuView.getWidth() = " + menuView.getWidth()); 108 | return doScroll(distanceX); 109 | } 110 | 111 | //菜单未被拉出,往左可滑 112 | if (currX <= 0 && distanceX < 0) { 113 | Log.v(TAG, "currX = " + currX + " ,distanceX = " + distanceX + " ,menuView.getWidth() = " + menuView.getWidth()); 114 | return doScroll(distanceX); 115 | } 116 | 117 | preTouchX = nowTouchX; 118 | preTouchY = nowTouchY; 119 | } 120 | break; 121 | case MotionEvent.ACTION_UP: 122 | Log.i(TAG, "ACTION_UP.."); 123 | if (null != menuView) 124 | startAutoScroll(); 125 | break; 126 | } 127 | return super.onTouchEvent(event); 128 | } 129 | 130 | 131 | private void startAutoScroll() { 132 | int scrollX = getScrollX(); 133 | Log.i(TAG, "startAutoScroll..scrollX = " + scrollX); 134 | if (scrollX >= menuView.getWidth() * 0.5) { 135 | smoothOpenMenu(); 136 | } else { 137 | smoothCloseMenu(); 138 | } 139 | } 140 | 141 | private void smoothOpenMenu() { 142 | smoothScrollTo(menuView.getWidth(), 0); 143 | } 144 | 145 | public void smoothCloseMenu() { 146 | smoothScrollTo(0, 0); 147 | } 148 | 149 | private boolean doScroll(float distanceX) { 150 | setScrollDistance(distanceX); 151 | preTouchX = nowTouchX; 152 | return true; 153 | } 154 | 155 | /** 156 | * 设置当次滚动的距离 157 | * 158 | * @param distanceX 159 | */ 160 | private void setScrollDistance(float distanceX) { 161 | float X = -getScrollX(); 162 | mScroller.setFinalX((-(int) (X + distanceX))); 163 | invalidate(); 164 | } 165 | 166 | 167 | @Override 168 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 169 | // Log.d(TAG, "contentView.getWidth() = " + contentView.getWidth() + "contentView.getHeight() = " + contentView.getHeight()); 170 | super.onLayout(changed, left, top, right, bottom); 171 | 172 | int contentViewWidth = contentView.getWidth(); 173 | int contentViewHeight = contentView.getHeight(); 174 | if (contentView != null && contentViewWidth != 0 && contentViewHeight != 0) { 175 | contentView.layout(0, 0, contentView.getWidth(), contentView.getHeight()); 176 | 177 | if (menuView != null) { 178 | int menuViewWidth = menuView.getWidth(); 179 | menuView.layout(contentViewWidth, 0, contentViewWidth + menuViewWidth, contentViewHeight); 180 | } 181 | } 182 | } 183 | 184 | /** 185 | * 获取menuView 186 | * 187 | * @return 188 | */ 189 | public LinearLayout getMenuView() { 190 | return menuView; 191 | } 192 | 193 | //调用此方法滚动到目标位置 194 | public void smoothScrollTo(int fx, int fy) { 195 | int dx = fx - mScroller.getFinalX(); 196 | int dy = fy - mScroller.getFinalY(); 197 | smoothScrollBy(dx, dy); 198 | } 199 | 200 | //调用此方法设置滚动的相对偏移 201 | public void smoothScrollBy(int dx, int dy) { 202 | 203 | //设置mScroller的滚动偏移量 204 | mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy); 205 | invalidate();//这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果 206 | } 207 | 208 | @Override 209 | public void computeScroll() { 210 | 211 | //先判断mScroller滚动是否完成 212 | if (mScroller.computeScrollOffset()) { 213 | 214 | //这里调用View的scrollTo()完成实际的滚动 215 | scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 216 | 217 | //必须调用该方法,否则不一定能看到滚动效果 218 | postInvalidate(); 219 | } 220 | super.computeScroll(); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /library/src/main/java/com/library/widget/defaultimple/DefaultFooterView.java: -------------------------------------------------------------------------------- 1 | package com.library.widget.defaultimple; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.ProgressBar; 6 | import android.widget.RelativeLayout; 7 | import android.widget.TextView; 8 | 9 | import com.library.widget.R; 10 | import com.library.widget.baseview.FooterView; 11 | 12 | /** 13 | * Created by zhangyu on 2016/11/18. 14 | */ 15 | 16 | public class DefaultFooterView extends FooterView { 17 | 18 | public TextView hintText; 19 | private ProgressBar progressBar; 20 | 21 | public DefaultFooterView(Context context) { 22 | super(context); 23 | } 24 | 25 | public DefaultFooterView(Context context, AttributeSet attrs) { 26 | super(context, attrs); 27 | } 28 | 29 | public DefaultFooterView(Context context, AttributeSet attrs, int defStyleAttr) { 30 | super(context, attrs, defStyleAttr); 31 | } 32 | 33 | @Override 34 | public int getLayoutView() { 35 | return R.layout.pull_to_refresh_footer; 36 | } 37 | 38 | @Override 39 | public void initView() { 40 | hintText = (TextView) findViewById(R.id.ptrf_tv); 41 | progressBar = (ProgressBar) findViewById(R.id.ptrf_progress_bar); 42 | } 43 | 44 | @Override 45 | public RelativeLayout getRootContainer() { 46 | return (RelativeLayout) findViewById(R.id.footer_container); 47 | } 48 | 49 | @Override 50 | public void setLoadingViewState() { 51 | progressBar.setVisibility(VISIBLE); 52 | hintText.setText("正在加载.."); 53 | } 54 | 55 | @Override 56 | public void setPullingViewState() { 57 | progressBar.setVisibility(INVISIBLE); 58 | hintText.setText("上拉加载更多"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /library/src/main/java/com/library/widget/defaultimple/DefaultHeaderView.java: -------------------------------------------------------------------------------- 1 | package com.library.widget.defaultimple; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.ProgressBar; 6 | import android.widget.RelativeLayout; 7 | import android.widget.TextView; 8 | 9 | import com.library.widget.R; 10 | import com.library.widget.baseview.HeaderView; 11 | 12 | /** 13 | * Created by zhangyu on 2016/11/18. 14 | */ 15 | 16 | public class DefaultHeaderView extends HeaderView { 17 | public TextView hintText; 18 | private ProgressBar progressBar; 19 | public DefaultHeaderView(Context context) { 20 | super(context); 21 | } 22 | 23 | public DefaultHeaderView(Context context, AttributeSet attrs) { 24 | super(context, attrs); 25 | } 26 | 27 | public DefaultHeaderView(Context context, AttributeSet attrs, int defStyleAttr) { 28 | super(context, attrs, defStyleAttr); 29 | } 30 | 31 | @Override 32 | public void initView() { 33 | hintText = (TextView) findViewById(R.id.ptrh_tv); 34 | progressBar = (ProgressBar) findViewById(R.id.ptrh_progress_bar); 35 | } 36 | 37 | @Override 38 | public RelativeLayout getRootContainer() { 39 | return (RelativeLayout) findViewById(R.id.header_container); 40 | } 41 | 42 | @Override 43 | public int getLayoutView() { 44 | return R.layout.pull_to_refresh_header; 45 | } 46 | 47 | @Override 48 | public void setRefreshingViewState() { 49 | progressBar.setVisibility(VISIBLE); 50 | hintText.setText("正在刷新.."); 51 | } 52 | 53 | @Override 54 | public void setPullingViewState() { 55 | progressBar.setVisibility(INVISIBLE); 56 | hintText.setText("下拉刷新"); 57 | } 58 | 59 | @Override 60 | public void setReadyViewState() { 61 | progressBar.setVisibility(INVISIBLE); 62 | hintText.setText("放开刷新"); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /library/src/main/java/com/library/widget/interfaces/OnMenuClickListener.java: -------------------------------------------------------------------------------- 1 | package com.library.widget.interfaces; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * Created by zhangyu on 2016/11/9. 7 | */ 8 | public interface OnMenuClickListener { 9 | /** 10 | * 菜单点击监听 11 | * @param view 被点击菜单view 12 | * @param position 被点击菜单所在item的位置 13 | */ 14 | void onMenuClick(View view,int position); 15 | } 16 | -------------------------------------------------------------------------------- /library/src/main/res/layout/pull_to_refresh_footer.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 26 | -------------------------------------------------------------------------------- /library/src/main/res/layout/pull_to_refresh_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 26 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Library 3 | 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | --------------------------------------------------------------------------------