一、概述

这篇文章记录:

  • recyclerView的基本用法
  • 为recyclerview添加点击事件
  • 添加上拉加载更多
  • 拖拽排序&滑动删除(初稿)

二、recyclerview基本用法:

  • 写item和recyclerview布局
  • 写Adapter
  • 绑定数据、适配器、recyclerview

1、基本操作

1
2
3
4
5
6
7
8
9
10
11
12
	//1、找到控件
mRecyclerView = findView(R.id.id_recyclerview);
//2、设置布局管理器
mRecyclerView.setLayoutManager(layout);
//3、设置adapter
mRecyclerView.setAdapter(adapter)
//4、设置Item动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//5、添加分割线
mRecyclerView.addItemDecoration(new DividerItemDecoration( getActivity(), DividerItemDecoration.HORIZONTAL_LIST));
//6、保持固定的大小,提高性能
mRecyclerView.setHasFixedSize(true);

2、适配器&ViewHolder

  • 继承RecyclerView.Adapter
  • 复写下面三个方法:
1
2
3
4
5
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i){}

public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i){}

public int getItemCount(){}
  • 写ViewHolder继承RecyclerView.ViewHolder

3、Click和LongClick事件

  • 1、在Adapter中定义如下接口:
1
2
3
public static interface OnRecyclerViewItemClickListener {
void onItemClick(View view , String data);
}
  • 2、在onCreateViewHolder()中为每个item添加点击事件
1
2
3
4
5
6
7
8
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item, viewGroup, false);
ViewHolder vh = new ViewHolder(view);
//将创建的View注册点击事件
view.setOnClickListener(this);
return vh;
}
  • 3、将点击事件转移给外面的调用者:
    1
    2
    3
    4
    5
    6
    7
     @Override
    public void onClick(View v) {
    if (mOnItemClickListener != null) {
    //注意这里使用getTag方法获取数据
    mOnItemClickListener.onItemClick(v,(String)v.getTag());
    }
    }

注意: onBindViewHolder()方法中设置和item相关的数据

1
2
3
4
5
6
7
   
@Override
public void onBindViewHolder(ViewHolder viewHolder, int position) {
viewHolder.mTextView.setText(datas[position]);
//将数据保存在itemView的Tag中,以便点击时进行获取
viewHolder.itemView.setTag(datas[position]);
}
  • 4、最后暴露给外面的调用者,定义一个设置Listener的方法()
1
2
3
public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) {
this.mOnItemClickListener = listener;
}

4、上拉加载更多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int lastVisibleItem = ((LinearLayoutManager) mLayoutManager).findLastVisibleItemPosition();
int totalItemCount = mLayoutManager.getItemCount();
//lastVisibleItem >= totalItemCount - 4 表示剩下4个item自动加载,各位自由选择
// dy>0 表示向下滑动
if (lastVisibleItem >= totalItemCount - 4 && dy > 0) {
if(isLoadingMore){
Log.d(TAG,"ignore manually update!");
} else{
loadPage();//这里多线程也要手动控制isLoadingMore
isLoadingMore = false;
}
}
}
});

5、ItemTouchHelper

This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView(from google docs)

  • 类的大概声明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class ItemTouchHelper extends RecyclerView.ItemDecoration
    implements RecyclerView.OnChildAttachStateChangeListener {


    //构造函数
    public ItemTouchHelper(Callback callback) {//传入一个Callback,两个内部类可用
    mCallback = callback;
    }
    //与recyclerview绑定
    public void attachToRecyclerView(RecyclerView recyclerView){}

    //some code here

    public abstract static class Callback {} //嵌套类
    public abstract static class SimpleCallback extends Callback{} //嵌套类
    }
  • SimpleCallback

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    /**
    *dragDirs- 表示拖拽的方向,有六个类型的值:LEFT、RIGHT、START、END、UP、DOWN
    *swipeDirs- 表示滑动的方向,有六个类型的值:LEFT、RIGHT、START、END、UP、DOWN
    */

    ItemTouchHelper.Callback mCallback = new ItemTouchHelper.SimpleCallback(int dragDirs, int swipeDirs) {
    /**
    * @param recyclerView
    * @param viewHolder 拖动的ViewHolder
    * @param target 目标位置的ViewHolder
    * @return
    */

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
    //...
    return false;
    }
    /**
    * @param viewHolder 滑动的ViewHolder
    * @param direction 滑动的方向
    */

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
    //...
    }
    };
  • Examples

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//0则不执行拖动或者滑动
ItemTouchHelper.Callback mCallback = new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP|ItemTouchHelper.DOWN,ItemTouchHelper.RIGHT) {

@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAdapterPosition();//得到拖动ViewHolder的position
int toPosition = target.getAdapterPosition();//得到目标ViewHolder的position
if (fromPosition < toPosition) {
//分别把中间所有的item的位置重新交换
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(datas, i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(datas, i, i - 1);
}
}
mAdapter.notifyItemMoved(fromPosition, toPosition);
//返回true表示执行拖动
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
datas.remove(position);
mAdapter.notifyItemRemoved(position);
}
@Override//画每一个Item时回调
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
if(actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
//滑动时改变Item的透明度
final float alpha = 1 - Math.abs(dX) / (float)viewHolder.itemView.getWidth();
viewHolder.itemView.setAlpha(alpha);
viewHolder.itemView.setTranslationX(dX);
}
}
};
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mCallback);
itemTouchHelper.attachToRecyclerView(mRecyclerView);

参考链接:

https://github.com/devunwired/recyclerview-playground

https://github.com/AleBarreto/DragRecyclerView.git

概述:

前几天看到这样一个效果

http://7xqumq.com1.z0.glb.clouddn.com/duwei_nestedscroll.gif

很想知道它的实现原理,于是看了一下源码,学到了一个新的东西:嵌套滑动

背景

在传统的View的事件分发机制中,事件一级一级的传递下去,直到被感兴趣的View拦截并消耗处理,如果所有的View都不拦截处理,那么最后一个View将会抛弃该事件。即:一个事件如果传给了子View那么父View将不可能再得到该事件。

类:

为了实现嵌套循环,5.0中提供了两个接口两个类:

1
2
3
4
5
6
7
8
9
10
11
//子View需实现的接口
NestedScrollingChild

//父View实现的接口
NestedScrollingParent

//子View的帮助类
NestedScrollingChildHelper

//父View 的帮助类
NestedScrollingParentHelper

主要方法

1
2
3
4
5
6
7
8
9
10
11
12
13

/**
* @param 水平滚动的像素值
* @param 垂直滚动的距离像素值
* @param consumed[0] :dx消耗掉的部分
* consumed[1] dy消费的部分
* @param offsetInWindow Optional. If not null, on return this will contain the offset
* in local view coordinates of this view from before this operation
* to after it completes. View implementations may use this to adjust
* expected input coordinate tracking.
* @return 如果父view消费了一些或全部,则返回true
*/

public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
ox = event.getX();
oy = event.getY();
//开始滑动!!!!!!!!!!
startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL | ViewCompat.SCROLL_AXIS_VERTICAL);
break;
case MotionEvent.ACTION_MOVE://在MOVE中
//结束点
float clampedX = event.getX();
float clampedY = event.getY();
//移动的距离
int dx = (int) (clampedX - ox);
int dy = (int) (clampedY - oy);
//分发触屏事件给父类处理
if (dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow)) {
//减掉父类消耗的距离
dx -= consumed[0];//consumed[0] will contain the consumed component of dx
dy -= consumed[1];//consumed[1] the consumed dy
}
offsetLeftAndRight(dx);//Offset this view's horizontal location by the specified amount of pixels
offsetTopAndBottom(dy);//通过指定像素来弥补控件的垂直位置
break;
case MotionEvent.ACTION_UP:
stopNestedScroll();//结束滑动!!!!!!!!
break;
}
return true;/*super.onTouchEvent(event);*/
}
1
2
3
4
5
6
7
8
9

/**
*子类滑动事件分发回调
* @param target View that initiated the nested scroll
* @param dx Horizontal scroll distance in pixels
* @param dy Vertical scroll distance in pixels
* @param consumed Output. The horizontal and vertical scroll distance consumed by this parent
*/

public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//子类滑动事件分发回调dispatchNestedPreScroll
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
if (dx > 0) {//水平滚动距离大于0
if (target.getRight() + dx > getWidth()) {//子View右边+移动的距离越界了(父View)
dx = target.getRight() + dx - getWidth();//越界这么多
offsetLeftAndRight(dx);//父View偏移这么多
consumed[0] += dx; //将消耗掉的距离返回给子类
}
} else {//水平滚动距离<0
if (target.getLeft() + dx < 0) {
dx = target.getLeft() + dx;
offsetLeftAndRight(dx);
consumed[0] += dx;
}
}
if (dy > 0) {
if (target.getBottom() + dy > getHeight()) {
dy = target.getBottom() +dy- getHeight();
offsetTopAndBottom(dy);
consumed[1] += dy;
}
} else {
if (target.getTop() + dy < 0) {
dy = target.getTop() + dy;
offsetTopAndBottom(dy);
consumed[1] += dy;
}
}
}

函数的回调关系如下:

左边的子View回调右边的父View的方法

http://7xqumq.com1.z0.glb.clouddn.com/duwei_nestedScroll.png

参考Demo:

https://github.com/liuxiangtian/NestedScrollDemo

概述

最近花时间写了两个自定义控件,一个是WaveLoadingView另外一个是PinWheel) 写Demo的时间没花多少,倒是往Jcenter上传花费了不少时间,在这过程中也遇到了许多的bug,不过在自己的努力下最后还是上传成功可以用了。下面就总结一下吧:

第一步:

1. 前往这里注册一个账号。

2.点击下图所示的Maven:


3.点击Add New Package:


4.依次填写以下信息:

  • Name:Library的名字(如:PinWheel)
  • Description:Library的描述信息(如:a custom dialog)
  • Licenses:Library遵循的协议(如:Apache2.0)
  • Tags :Library的标签(如:Android)
  • Website:Library的网站(写github项目地址就好)
  • Issues tracker:Issues的跟踪(写githubissues,如:https://github.com/codingWang/PinWheel/issues)
  • Version control:版本控制(如:git@github.com:codingWang/PinWheel.git)

填写完毕点击Create,OK。

第二步:

1. 新建项目(或已经有项目)并添加以下两句去Project 的 build.gradle

1
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'

2.在Library的build.gradle中依次添加如下:

1
2
3
4
5
6
7
8
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: 'com.jfrog.bintray'
version = "0.1"

def siteUrl = 'https://github.com/codingWang/PinWheel' // 项目地址!!!!
def gitUrl = 'git@github.com:codingWang/PinWheel.git' // git地址!!!

group = "com.duwei.pinwheel" //!!!!!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
install {
repositories.mavenInstaller {
// This generates POM.xml with proper parameters
pom {
project {
packaging 'aar'
name 'PinWheel'//!!!!!!!!!!!
url siteUrl
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id 'duwei' //!!!!!!!!!!!!
name 'duwei'//!!!!!!!!
email 'du2035392@gmail.com'//!!!!!!!!!
}
}
scm {
connection gitUrl
developerConnection gitUrl
url siteUrl
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}

task javadoc(type: Javadoc) {
options.encoding = "utf-8"//!!!!文档中文编码乱码问题解决
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
}

task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}

artifacts {
archives javadocJar
archives sourcesJar
}
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
bintray {
user = properties.getProperty("bintray.user")
key = properties.getProperty("bintray.apikey")
configurations = ['archives']
pkg {
repo = "maven"
name = "PinWheel" // Jcenter项目中Name的值
websiteUrl = siteUrl
vcsUrl = gitUrl
licenses = ["Apache-2.0"]
publish = true
}
}

直接复制上述内容添加到你的Library的build.grale就可以了,然后修改标!!!的地方

第三步:

打开项目的local.properties添加如下信息

1
2
bintray.user=duwei                                // bintray的用户有
bintray.apikey= // 该值可在bintray中点击Edite

http://7xqumq.com1.z0.glb.clouddn.com/duwei_edit.png
http://7xqumq.com1.z0.glb.clouddn.com/duwei_apikey.png

第四步:

在Android stuodio的Terminal终端依次执行以下命令

1
2
3
4
5
6
7
gradew javadocJar	

gradew sourcesJar

gradew install

gradew bintrayUpload

第五步:

引用Library

1
2
3
4
5
repositories {
maven {
url 'https://dl.bintray.com/duwei/maven/'
}
}
1
2
3
4
5
dependencies {

compile 'com.duwei.pinwheel:pinwheel:0.1'

}

一、概述

之前对于android插值器的理解仅仅是表面的,只知道它表示一种变化的趋势/形式,并且只会用简单的加速/减速/加减速插值器,昨天在看一个开源动画源码的时候看到了一个自定义插值器的代码,于是又收获学习到了许多。

二、系统插值器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
AccelerateDecelerateInterpolator	//加速减速插值器

AccelerateInterpolator //加速插值器

DecelerateInterpolator //减速插值器

LinearInterpolator //线性插值器

OvershootInterpolator //超越插值器-->向前甩一定值后再回到原来位置

AnticipateInterpolator //预先插值器(先这样翻译吧~~)-->开始的时候向后然后向前甩

AnticipateOvershootInterpolator //预先超过插值器-->开始的时候向后然后向前甩一定值后返回最后的值

BounceInterpolator //弹跳插值器-->动画结束的时候弹起

CycleInterpolator //循环插值器-->循环播放特定的次数,速率改变沿着正弦曲线

三、系统插值器分析

1、所有的系统插值器都继承自Interpolator类,而Interpolator又实现了TimeInterpolator接口:

1
2
3
public interface TimeInterpolator {
float getInterpolation(float input);
}

2、所以该方法是所有插值器的核心方法,先简单看看系统插值器的该方法:

1
2
3
4
//AccelerateDecelerateInterpolator
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
1
2
3
4
//LinearInterpolator
public float getInterpolation(float input) {
return input;
}

3、该方法返回的就是一个函数,根据函数我们可以得到函数的图像。这里,斜率应该就是加速度了。

四、关于自定义插值器

想自定义插值器只需要implements TimeInterpolator就可以拉,然后复写他的getInterpolation(float t);方法。但,难点在于运动函数图像的确定上面。如何精确合理的分析自己的动画的速度的变化规律,然后找到符合该变化规律的函数是一个困难的问题。在这里要重点关注一下。

一、概述

这些天看了很多开源项目的源码(自定义View方面),对属性动画也用的相对熟练一点了,下面就是对相关方法的一些总结:

二、常用方法总结

1
/**
*将指定的对象(target)的属性(propertyName)在值(values)间进行变换
**/
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values){}


/**
*只指定变化的范围
**/
public static ValueAnimator ofFloat(float... values) {}

1、ObjectAnimator继承自ValueAnimator,都有以下常用方法:

1

anim.setDuration(1000);//1秒
anim.setRepeatCount(-1);//无线循环
anim.setInterpolator(new AccelerateDecelerateInterpolator());//插值器
anim.start();//开始动画

2、动画集:

1

AnimatorSet set = new AnimatorSet();
set.playTogether(......);
set.setDuration(1000);
set.start();

//----------------------------------------------
set.plat().after(),with().befor();//相关方法

三、监听器

提供了2个监听器:

1
//Animator的内部类
public static interface AnimatorListener {

       void onAnimationStart(Animator animation);
       
       void onAnimationEnd(Animator animation);
       
       void onAnimationCancel(Animator animation);
       
       void onAnimationRepeat(Animator animation);
   }
1
public static interface AnimatorUpdateListener {
      
       void onAnimationUpdate(ValueAnimator animation);

 }