package com.loaderman.contactsrecycledemo;
/*
* This code is cloned from DefaultItemAnimator provided by support library v7-recyclerView
*
* Copyright (C) 2014 The Android Open Source Project
* Copyright (c) 2014-2015 Gabriele Mariotti.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.support.v4.animation.AnimatorCompatHelper;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SimpleItemAnimator;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
/**
* This implementation of {@link android.support.v7.widget.RecyclerView.ItemAnimator} provides basic
* animations on remove, add, and move events that happen to the items in
* a RecyclerView.
*
* @see android.support.v7.widget.RecyclerView#setItemAnimator(android.support.v7.widget.RecyclerView.ItemAnimator)
*/
public abstract class BaseItemAnimator extends SimpleItemAnimator {
/**
* RecyclerView
*/
protected RecyclerView mRecyclerView;
//------------------------------------------------------------
// Constructor
//------------------------------------------------------------
public BaseItemAnimator(RecyclerView recyclerView){
mRecyclerView = recyclerView;
}
//------------------------------------------------------------
// Default Item Animator
//------------------------------------------------------------
private static final boolean DEBUG = false;
private ArrayList<RecyclerView.ViewHolder> mPendingRemovals = new ArrayList<>();
private ArrayList<RecyclerView.ViewHolder> mPendingAdditions = new ArrayList<>();
private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();
private ArrayList<ChangeInfo> mPendingChanges = new ArrayList<>();
private ArrayList<ArrayList<RecyclerView.ViewHolder>> mAdditionsList = new ArrayList<>();
private ArrayList<ArrayList<MoveInfo>> mMovesList = new ArrayList<>();
private ArrayList<ArrayList<ChangeInfo>> mChangesList = new ArrayList<>();
protected ArrayList<RecyclerView.ViewHolder> mAddAnimations = new ArrayList<>();
protected ArrayList<RecyclerView.ViewHolder> mMoveAnimations = new ArrayList<>();
protected ArrayList<RecyclerView.ViewHolder> mRemoveAnimations = new ArrayList<>();
protected ArrayList<RecyclerView.ViewHolder> mChangeAnimations = new ArrayList<>();
private static class MoveInfo {
public RecyclerView.ViewHolder holder;
public int fromX, fromY, toX, toY;
private MoveInfo(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
this.holder = holder;
this.fromX = fromX;
this.fromY = fromY;
this.toX = toX;
this.toY = toY;
}
}
private static class ChangeInfo {
public RecyclerView.ViewHolder oldHolder, newHolder;
public int fromX, fromY, toX, toY;
private ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder) {
this.oldHolder = oldHolder;
this.newHolder = newHolder;
}
private ChangeInfo(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
int fromX, int fromY, int toX, int toY) {
this(oldHolder, newHolder);
this.fromX = fromX;
this.fromY = fromY;
this.toX = toX;
this.toY = toY;
}
@Override
public String toString() {
return "ChangeInfo{" +
"oldHolder=" + oldHolder +
", newHolder=" + newHolder +
", fromX=" + fromX +
", fromY=" + fromY +
", toX=" + toX +
", toY=" + toY +
‘}‘;
}
}
@Override
public void runPendingAnimations() {
boolean removalsPending = !mPendingRemovals.isEmpty();
boolean movesPending = !mPendingMoves.isEmpty();
boolean changesPending = !mPendingChanges.isEmpty();
boolean additionsPending = !mPendingAdditions.isEmpty();
if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
// nothing to animate
return;
}
// First, remove stuff
for (RecyclerView.ViewHolder holder : mPendingRemovals) {
animateRemoveImpl(holder);
}
mPendingRemovals.clear();
// Next, move stuff
if (movesPending) {
final ArrayList<MoveInfo> moves = new ArrayList<>();
moves.addAll(mPendingMoves);
mMovesList.add(moves);
mPendingMoves.clear();
Runnable mover = new Runnable() {
@Override
public void run() {
for (MoveInfo moveInfo : moves) {
animateMoveImpl(moveInfo.holder, moveInfo.fromX, moveInfo.fromY,
moveInfo.toX, moveInfo.toY);
}
moves.clear();
mMovesList.remove(moves);
}
};
if (removalsPending) {
View view = moves.get(0).holder.itemView;
ViewCompat.postOnAnimationDelayed(view, mover, getRemoveDuration());
} else {
mover.run();
}
}
// Next, change stuff, to run in parallel with move animations
if (changesPending) {
final ArrayList<ChangeInfo> changes = new ArrayList<>();
changes.addAll(mPendingChanges);
mChangesList.add(changes);
mPendingChanges.clear();
Runnable changer = new Runnable() {
@Override
public void run() {
for (ChangeInfo change : changes) {
animateChangeImpl(change);
}
changes.clear();
mChangesList.remove(changes);
}
};
if (removalsPending) {
RecyclerView.ViewHolder holder = changes.get(0).oldHolder;
ViewCompat.postOnAnimationDelayed(holder.itemView, changer, getRemoveDuration());
} else {
changer.run();
}
}
// Next, add stuff
if (additionsPending) {
final ArrayList<RecyclerView.ViewHolder> additions = new ArrayList<>();
additions.addAll(mPendingAdditions);
mAdditionsList.add(additions);
mPendingAdditions.clear();
Runnable adder = new Runnable() {
public void run() {
for (RecyclerView.ViewHolder holder : additions) {
animateAddImpl(holder);
}
additions.clear();
mAdditionsList.remove(additions);
}
};
if (removalsPending || movesPending || changesPending) {
long removeDuration = removalsPending ? getRemoveDuration() : 0;
long moveDuration = movesPending ? getMoveDuration() : 0;
long changeDuration = changesPending ? getChangeDuration() : 0;
long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
View view = additions.get(0).itemView;
ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
} else {
adder.run();
}
}
}
@Override
public boolean animateAdd(final RecyclerView.ViewHolder holder) {
resetAnimation(holder);
prepareAnimateAdd(holder);
ViewCompat.setAlpha(holder.itemView, 0);
mPendingAdditions.add(holder);
return true;
}
protected abstract void prepareAnimateAdd(final RecyclerView.ViewHolder holder);
protected abstract void animateAddImpl(final RecyclerView.ViewHolder holder);
@Override
public boolean animateRemove(final RecyclerView.ViewHolder holder) {
resetAnimation(holder);
mPendingRemovals.add(holder);
return true;
}
protected abstract void animateRemoveImpl(final RecyclerView.ViewHolder holder);
@Override
public boolean animateMove(final RecyclerView.ViewHolder holder, int fromX, int fromY,
int toX, int toY) {
final View view = holder.itemView;
fromX += ViewCompat.getTranslationX(holder.itemView);
fromY += ViewCompat.getTranslationY(holder.itemView);
resetAnimation(holder);
int deltaX = toX - fromX;
int deltaY = toY - fromY;
if (deltaX == 0 && deltaY == 0) {
dispatchMoveFinished(holder);
return false;
}
if (deltaX != 0) {
ViewCompat.setTranslationX(view, -deltaX);
}
if (deltaY != 0) {
ViewCompat.setTranslationY(view, -deltaY);
}
mPendingMoves.add(new MoveInfo(holder, fromX, fromY, toX, toY));
return true;
}
private void animateMoveImpl(final RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
final View view = holder.itemView;
final int deltaX = toX - fromX;
final int deltaY = toY - fromY;
if (deltaX != 0) {
ViewCompat.animate(view).translationX(0);
}
if (deltaY != 0) {
ViewCompat.animate(view).translationY(0);
}
// TODO: make EndActions end listeners instead, since end actions aren‘t called when
// vpas are canceled (and can‘t end them. why?)
// need listener functionality in VPACompat for this. Ick.
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
mMoveAnimations.add(holder);
animation.setDuration(getMoveDuration()).setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchMoveStarting(holder);
}
@Override
public void onAnimationCancel(View view) {
if (deltaX != 0) {
ViewCompat.setTranslationX(view, 0);
}
if (deltaY != 0) {
ViewCompat.setTranslationY(view, 0);
}
}
@Override
public void onAnimationEnd(View view) {
animation.setListener(null);
dispatchMoveFinished(holder);
mMoveAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
@Override
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder,
int fromX, int fromY, int toX, int toY) {
if (oldHolder == newHolder) {
// Don‘t know how to run change animations when the same view holder is re-used.
// run a move animation to handle position changes.
return animateMove(oldHolder, fromX, fromY, toX, toY);
}
final float prevTranslationX = ViewCompat.getTranslationX(oldHolder.itemView);
final float prevTranslationY = ViewCompat.getTranslationY(oldHolder.itemView);
final float prevAlpha = ViewCompat.getAlpha(oldHolder.itemView);
resetAnimation(oldHolder);
int deltaX = (int) (toX - fromX - prevTranslationX);
int deltaY = (int) (toY - fromY - prevTranslationY);
// recover prev translation state after ending animation
ViewCompat.setTranslationX(oldHolder.itemView, prevTranslationX);
ViewCompat.setTranslationY(oldHolder.itemView, prevTranslationY);
ViewCompat.setAlpha(oldHolder.itemView, prevAlpha);
if (newHolder != null) {
// carry over translation values
resetAnimation(newHolder);
ViewCompat.setTranslationX(newHolder.itemView, -deltaX);
ViewCompat.setTranslationY(newHolder.itemView, -deltaY);
ViewCompat.setAlpha(newHolder.itemView, 0);
}
mPendingChanges.add(new ChangeInfo(oldHolder, newHolder, fromX, fromY, toX, toY));
return true;
}
private void animateChangeImpl(final ChangeInfo changeInfo) {
final RecyclerView.ViewHolder holder = changeInfo.oldHolder;
final View view = holder == null ? null : holder.itemView;
final RecyclerView.ViewHolder newHolder = changeInfo.newHolder;
final View newView = newHolder != null ? newHolder.itemView : null;
if (view != null) {
final ViewPropertyAnimatorCompat oldViewAnim = ViewCompat.animate(view).setDuration(
getChangeDuration());
mChangeAnimations.add(changeInfo.oldHolder);
oldViewAnim.translationX(changeInfo.toX - changeInfo.fromX);
oldViewAnim.translationY(changeInfo.toY - changeInfo.fromY);
oldViewAnim.alpha(0).setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchChangeStarting(changeInfo.oldHolder, true);
}
@Override
public void onAnimationEnd(View view) {
oldViewAnim.setListener(null);
ViewCompat.setAlpha(view, 1);
ViewCompat.setTranslationX(view, 0);
ViewCompat.setTranslationY(view, 0);
dispatchChangeFinished(changeInfo.oldHolder, true);
mChangeAnimations.remove(changeInfo.oldHolder);
dispatchFinishedWhenDone();
}
}).start();
}
if (newView != null) {
final ViewPropertyAnimatorCompat newViewAnimation = ViewCompat.animate(newView);
mChangeAnimations.add(changeInfo.newHolder);
newViewAnimation.translationX(0).translationY(0).setDuration(getChangeDuration()).
alpha(1).setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchChangeStarting(changeInfo.newHolder, false);
}
@Override
public void onAnimationEnd(View view) {
newViewAnimation.setListener(null);
ViewCompat.setAlpha(newView, 1);
ViewCompat.setTranslationX(newView, 0);
ViewCompat.setTranslationY(newView, 0);
dispatchChangeFinished(changeInfo.newHolder, false);
mChangeAnimations.remove(changeInfo.newHolder);
dispatchFinishedWhenDone();
}
}).start();
}
}
private void endChangeAnimation(List<ChangeInfo> infoList, RecyclerView.ViewHolder item) {
for (int i = infoList.size() - 1; i >= 0; i--) {
ChangeInfo changeInfo = infoList.get(i);
if (endChangeAnimationIfNecessary(changeInfo, item)) {
if (changeInfo.oldHolder == null && changeInfo.newHolder == null) {
infoList.remove(changeInfo);
}
}
}
}
private void endChangeAnimationIfNecessary(ChangeInfo changeInfo) {
if (changeInfo.oldHolder != null) {
endChangeAnimationIfNecessary(changeInfo, changeInfo.oldHolder);
}
if (changeInfo.newHolder != null) {
endChangeAnimationIfNecessary(changeInfo, changeInfo.newHolder);
}
}
private boolean endChangeAnimationIfNecessary(ChangeInfo changeInfo, RecyclerView.ViewHolder item) {
boolean oldItem = false;
if (changeInfo.newHolder == item) {
changeInfo.newHolder = null;
} else if (changeInfo.oldHolder == item) {
changeInfo.oldHolder = null;
oldItem = true;
} else {
return false;
}
ViewCompat.setAlpha(item.itemView, 1);
ViewCompat.setTranslationX(item.itemView, 0);
ViewCompat.setTranslationY(item.itemView, 0);
dispatchChangeFinished(item, oldItem);
return true;
}
@Override
public void endAnimation(RecyclerView.ViewHolder item) {
final View view = item.itemView;
// this will trigger end callback which should set properties to their target values.
ViewCompat.animate(view).cancel();
// TODO if some other animations are chained to end, how do we cancel them as well?
for (int i = mPendingMoves.size() - 1; i >= 0; i--) {
MoveInfo moveInfo = mPendingMoves.get(i);
if (moveInfo.holder == item) {
ViewCompat.setTranslationY(view, 0);
ViewCompat.setTranslationX(view, 0);
dispatchMoveFinished(item);
mPendingMoves.remove(i);
}
}
endChangeAnimation(mPendingChanges, item);
if (mPendingRemovals.remove(item)) {
ViewCompat.setAlpha(view, 1);
dispatchRemoveFinished(item);
}
if (mPendingAdditions.remove(item)) {
ViewCompat.setAlpha(view, 1);
dispatchAddFinished(item);
}
for (int i = mChangesList.size() - 1; i >= 0; i--) {
ArrayList<ChangeInfo> changes = mChangesList.get(i);
endChangeAnimation(changes, item);
if (changes.isEmpty()) {
mChangesList.remove(i);
}
}
for (int i = mMovesList.size() - 1; i >= 0; i--) {
ArrayList<MoveInfo> moves = mMovesList.get(i);
for (int j = moves.size() - 1; j >= 0; j--) {
MoveInfo moveInfo = moves.get(j);
if (moveInfo.holder == item) {
ViewCompat.setTranslationY(view, 0);
ViewCompat.setTranslationX(view, 0);
dispatchMoveFinished(item);
moves.remove(j);
if (moves.isEmpty()) {
mMovesList.remove(i);
}
break;
}
}
}
for (int i = mAdditionsList.size() - 1; i >= 0; i--) {
ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i);
if (additions.remove(item)) {
ViewCompat.setAlpha(view, 1);
dispatchAddFinished(item);
if (additions.isEmpty()) {
mAdditionsList.remove(i);
}
}
}
// animations should be ended by the cancel above.
//noinspection PointlessBooleanExpression,ConstantConditions
if (mRemoveAnimations.remove(item) && DEBUG) {
throw new IllegalStateException("after animation is cancelled, item should not be in "
+ "mRemoveAnimations list");
}
//noinspection PointlessBooleanExpression,ConstantConditions
if (mAddAnimations.remove(item) && DEBUG) {
throw new IllegalStateException("after animation is cancelled, item should not be in "
+ "mAddAnimations list");
}
//noinspection PointlessBooleanExpression,ConstantConditions
if (mChangeAnimations.remove(item) && DEBUG) {
throw new IllegalStateException("after animation is cancelled, item should not be in "
+ "mChangeAnimations list");
}
//noinspection PointlessBooleanExpression,ConstantConditions
if (mMoveAnimations.remove(item) && DEBUG) {
throw new IllegalStateException("after animation is cancelled, item should not be in "
+ "mMoveAnimations list");
}
dispatchFinishedWhenDone();
}
private void resetAnimation(RecyclerView.ViewHolder holder) {
AnimatorCompatHelper.clearInterpolator(holder.itemView);
endAnimation(holder);
}
@Override
public boolean isRunning() {
return (!mPendingAdditions.isEmpty() ||
!mPendingChanges.isEmpty() ||
!mPendingMoves.isEmpty() ||
!mPendingRemovals.isEmpty() ||
!mMoveAnimations.isEmpty() ||
!mRemoveAnimations.isEmpty() ||
!mAddAnimations.isEmpty() ||
!mChangeAnimations.isEmpty() ||
!mMovesList.isEmpty() ||
!mAdditionsList.isEmpty() ||
!mChangesList.isEmpty());
}
/**
* Check the state of currently pending and running animations. If there are none
* pending/running, call {@link #dispatchAnimationsFinished()} to notify any
* listeners.
*/
protected void dispatchFinishedWhenDone() {
if (!isRunning()) {
dispatchAnimationsFinished();
}
}
@Override
public void endAnimations() {
int count = mPendingMoves.size();
for (int i = count - 1; i >= 0; i--) {
MoveInfo item = mPendingMoves.get(i);
View view = item.holder.itemView;
ViewCompat.setTranslationY(view, 0);
ViewCompat.setTranslationX(view, 0);
dispatchMoveFinished(item.holder);
mPendingMoves.remove(i);
}
count = mPendingRemovals.size();
for (int i = count - 1; i >= 0; i--) {
RecyclerView.ViewHolder item = mPendingRemovals.get(i);
dispatchRemoveFinished(item);
mPendingRemovals.remove(i);
}
count = mPendingAdditions.size();
for (int i = count - 1; i >= 0; i--) {
RecyclerView.ViewHolder item = mPendingAdditions.get(i);
View view = item.itemView;
ViewCompat.setAlpha(view, 1);
dispatchAddFinished(item);
mPendingAdditions.remove(i);
}
count = mPendingChanges.size();
for (int i = count - 1; i >= 0; i--) {
endChangeAnimationIfNecessary(mPendingChanges.get(i));
}
mPendingChanges.clear();
if (!isRunning()) {
return;
}
int listCount = mMovesList.size();
for (int i = listCount - 1; i >= 0; i--) {
ArrayList<MoveInfo> moves = mMovesList.get(i);
count = moves.size();
for (int j = count - 1; j >= 0; j--) {
MoveInfo moveInfo = moves.get(j);
RecyclerView.ViewHolder item = moveInfo.holder;
View view = item.itemView;
ViewCompat.setTranslationY(view, 0);
ViewCompat.setTranslationX(view, 0);
dispatchMoveFinished(moveInfo.holder);
moves.remove(j);
if (moves.isEmpty()) {
mMovesList.remove(moves);
}
}
}
listCount = mAdditionsList.size();
for (int i = listCount - 1; i >= 0; i--) {
ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i);
count = additions.size();
for (int j = count - 1; j >= 0; j--) {
RecyclerView.ViewHolder item = additions.get(j);
View view = item.itemView;
ViewCompat.setAlpha(view, 1);
dispatchAddFinished(item);
additions.remove(j);
if (additions.isEmpty()) {
mAdditionsList.remove(additions);
}
}
}
listCount = mChangesList.size();
for (int i = listCount - 1; i >= 0; i--) {
ArrayList<ChangeInfo> changes = mChangesList.get(i);
count = changes.size();
for (int j = count - 1; j >= 0; j--) {
endChangeAnimationIfNecessary(changes.get(j));
if (changes.isEmpty()) {
mChangesList.remove(changes);
}
}
}
cancelAll(mRemoveAnimations);
cancelAll(mMoveAnimations);
cancelAll(mAddAnimations);
cancelAll(mChangeAnimations);
dispatchAnimationsFinished();
}
void cancelAll(List<RecyclerView.ViewHolder> viewHolders) {
for (int i = viewHolders.size() - 1; i >= 0; i--) {
ViewCompat.animate(viewHolders.get(i).itemView).cancel();
}
}
protected static class VpaListenerAdapter implements ViewPropertyAnimatorListener {
@Override
public void onAnimationStart(View view) {}
@Override
public void onAnimationEnd(View view) {}
@Override
public void onAnimationCancel(View view) {}
};
}
package com.loaderman.contactsrecycledemo;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
/**
* @author amulya
* @datetime 14 Oct 2014, 5:20 PM
*/
public class ColorGenerator {
public static ColorGenerator DEFAULT;
public static ColorGenerator MATERIAL;
static {
DEFAULT = create(Arrays.asList(
0xfff16364,
0xfff58559,
0xfff9a43e,
0xffe4c62e,
0xff67bf74,
0xff59a2be,
0xff2093cd,
0xffad62a7,
0xff805781
));
MATERIAL = create(Arrays.asList(
0xffe57373,
0xfff06292,
0xffba68c8,
0xff9575cd,
0xff7986cb,
0xff64b5f6,
0xff4fc3f7,
0xff4dd0e1,
0xff4db6ac,
0xff81c784,
0xffaed581,
0xffff8a65,
0xffd4e157,
0xffffd54f,
0xffffb74d,
0xffa1887f,
0xff90a4ae
));
}
private final List<Integer> mColors;
private final Random mRandom;
public static ColorGenerator create(List<Integer> colorList) {
return new ColorGenerator(colorList);
}
private ColorGenerator(List<Integer> colorList) {
mColors = colorList;
mRandom = new Random(System.currentTimeMillis());
}
public int getRandomColor() {
return mColors.get(mRandom.nextInt(mColors.size()));
}
public int getColor(Object key) {
return mColors.get(Math.abs(key.hashCode()) % mColors.size());
}
}
package com.loaderman.contactsrecycledemo;
import android.graphics.Color;
import android.graphics.Paint;
/**
* Created by MQ on 2017/5/18.
*/
public class ColorUtil {
/**
* 根据数据位置来给Paint循环设置颜色
*
* @param mPaint Paint
* @param position position
*/
public static void setPaintColor(Paint mPaint, int position) {
int pos = position % 6;
switch (pos) {
case 0:
mPaint.setColor(Color.parseColor("#EC5745"));
break;
case 1:
mPaint.setColor(Color.parseColor("#377caf"));
break;
case 2:
mPaint.setColor(Color.parseColor("#4ebcd3"));
break;
case 3:
mPaint.setColor(Color.parseColor("#6fb30d"));
break;
case 4:
mPaint.setColor(Color.parseColor("#FFA500"));
break;
case 5:
mPaint.setColor(Color.parseColor("#bf9e5a"));
break;
}
}
}
package com.loaderman.contactsrecycledemo;
import android.view.View;
import com.github.promeg.pinyinhelper.Pinyin;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Created by MQ on 2017/5/3.
*/
public class CommonUtil {
/**
* 测量View的宽高
*
* @param view View
*/
public static void measureWidthAndHeight(View view) {
int w = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int h = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(w, h);
}
/**
* 对数据进行排序
*
* @param list 要进行排序的数据源
*/
public static void sortData(List<ContactBean> list) {
if (list == null || list.size() == 0) return;
for (int i = 0; i < list.size(); i++) {
ContactBean bean = list.get(i);
String tag = Pinyin.toPinyin(bean.getName().substring(0, 1).charAt(0)).substring(0, 1);
if (tag.matches("[A-Z]")) {
bean.setIndexTag(tag);
} else {
bean.setIndexTag("#");
}
}
Collections.sort(list, new Comparator<ContactBean>() {
@Override
public int compare(ContactBean o1, ContactBean o2) {
if ("#".equals(o1.getIndexTag())) {
return 1;
} else if ("#".equals(o2.getIndexTag())) {
return -1;
} else {
return o1.getIndexTag().compareTo(o2.getIndexTag());
}
}
});
}
/**
* @param beans 数据源
* @return tags 返回一个包含所有Tag字母在内的字符串
*/
public static String getTags(List<ContactBean> beans) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < beans.size(); i++) {
if (!builder.toString().contains(beans.get(i).getIndexTag())) {
builder.append(beans.get(i).getIndexTag());
}
}
return builder.toString();
}
}
package com.loaderman.contactsrecycledemo;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
/**
* Created by MQ on 2017/5/8.
*/
public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.MyRecycleHolder> {
private List<ContactBean> contactBeanList;
private Context mContext;
// declare the color generator and drawable builder
private ColorGenerator mColorGenerator = ColorGenerator.MATERIAL;
private TextDrawable.IBuilder mDrawableBuilder = TextDrawable.builder().round();
public ContactAdapter(Context context) {
this.mContext = context;
contactBeanList = new ArrayList<>();
}
public void addAll(List<ContactBean> beans) {
if (contactBeanList.size() > 0) {
contactBeanList.clear();
}
contactBeanList.addAll(beans);
notifyDataSetChanged();
}
public void add(ContactBean bean, int position) {
contactBeanList.add(position, bean);
notifyItemInserted(position);
}
public void add(ContactBean bean) {
contactBeanList.add(bean);
notifyItemChanged(contactBeanList.size() - 1);
}
@Override
public MyRecycleHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycle_item_layout, parent, false);
return new MyRecycleHolder(view);
}
@Override
public void onBindViewHolder(MyRecycleHolder holder, int position) {
if (contactBeanList == null || contactBeanList.size() == 0 || contactBeanList.size() <= position)
return;
ContactBean bean = contactBeanList.get(position);
if (bean != null) {
holder.tv_name.setText(bean.getName());
TextDrawable drawable = mDrawableBuilder.build(String.valueOf(bean.getName().charAt(0)), mColorGenerator.getColor(bean.getName()));
holder.iv_img.setImageDrawable(drawable);
}
}
@Override
public int getItemCount() {
return contactBeanList.size();
}
public static class MyRecycleHolder extends RecyclerView.ViewHolder {
public final TextView tv_name;
public final ImageView iv_img;
public MyRecycleHolder(View itemView) {
super(itemView);
tv_name = (TextView) itemView.findViewById(R.id.tv_name);
iv_img = (ImageView) itemView.findViewById(R.id.iv_img);
}
}
}
package com.loaderman.contactsrecycledemo;
/**
* Created by MQ on 2017/5/16.
*/
public class ContactBean {
private String indexTag;
private String name;
public String getIndexTag() {
return indexTag;
}
public void setIndexTag(String indexTag) {
this.indexTag = indexTag;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.loaderman.contactsrecycledemo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.View;
import java.util.List;
/**
* Created by MQ on 2017/5/8.
*/
public class CustomItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
private List<ContactBean> mBeans;
private static final int dividerHeight = 80;
private Context mContext;
private final Rect mBounds = new Rect();
private String tagsStr;
public void setDatas(List<ContactBean> mBeans, String tagsStr) {
this.mBeans = mBeans;
this.tagsStr = tagsStr;
}
public CustomItemDecoration(Context mContext) {
this.mContext = mContext;
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setTextAlign(Paint.Align.CENTER);
}
@Override
public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
if (parent.getLayoutManager() == null) {
return;
}
canvas.save();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int position = params.getViewLayoutPosition();
if (mBeans == null || mBeans.size() == 0 || mBeans.size() <= position || position < 0) {
continue;
}
if (position == 0) {
//第一条数据有bar
drawTitleBar(canvas, parent, child, mBeans.get(position), tagsStr.indexOf(mBeans.get(position).getIndexTag()));
} else if (position > 0) {
if (TextUtils.isEmpty(mBeans.get(position).getIndexTag())) continue;
//与上一条数据中的tag不同时,该显示bar了
if (!mBeans.get(position).getIndexTag().equals(mBeans.get(position - 1).getIndexTag())) {
drawTitleBar(canvas, parent, child, mBeans.get(position), tagsStr.indexOf(mBeans.get(position).getIndexTag()));
}
}
}
canvas.restore();
}
/**
* 绘制bar
*
* @param canvas Canvas
* @param parent RecyclerView
* @param child ItemView
*/
private void drawTitleBar(Canvas canvas, RecyclerView parent, View child, ContactBean bean, int position) {
final int left = 0;
final int right = parent.getWidth();
//返回一个包含Decoration和Margin在内的Rect
parent.getDecoratedBoundsWithMargins(child, mBounds);
final int top = mBounds.top;
final int bottom = mBounds.top + Math.round(ViewCompat.getTranslationY(child)) + dividerHeight;
mPaint.setColor(Color.WHITE);
canvas.drawRect(left, top, right, bottom, mPaint);
//根据位置不断变换Paint的颜色
ColorUtil.setPaintColor(mPaint, position);
mPaint.setTextSize(40);
canvas.drawCircle(DpUtil.dp2px(mContext, 42.5f), bottom - dividerHeight / 2, 35, mPaint);
mPaint.setColor(Color.WHITE);
canvas.drawText(bean.getIndexTag(), DpUtil.dp2px(mContext, 42.5f), bottom - dividerHeight / 3, mPaint);
}
@Override
public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
//用来绘制悬浮框
int position = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
if (mBeans == null || mBeans.size() == 0 || mBeans.size() <= position || position < 0) {
return;
}
final int bottom = parent.getPaddingTop() + dividerHeight;
mPaint.setColor(Color.WHITE);
canvas.drawRect(parent.getLeft(), parent.getPaddingTop(), parent.getRight() - parent.getPaddingRight(), parent.getPaddingTop() + dividerHeight, mPaint);
ColorUtil.setPaintColor(mPaint, tagsStr.indexOf(mBeans.get(position).getIndexTag()));
mPaint.setTextSize(40);
canvas.drawCircle(DpUtil.dp2px(mContext, 42.5f), bottom - dividerHeight / 2, 35, mPaint);
mPaint.setColor(Color.WHITE);
canvas.drawText(mBeans.get(position).getIndexTag(), DpUtil.dp2px(mContext, 42.5f), bottom - dividerHeight / 3, mPaint);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view);
if (mBeans == null || mBeans.size() == 0 || mBeans.size() <= position || position < 0) {
super.getItemOffsets(outRect, view, parent, state);
return;
}
if (position == 0) {
//第一条数据有bar
outRect.set(0, dividerHeight, 0, 0);
} else if (position > 0) {
if (TextUtils.isEmpty(mBeans.get(position).getIndexTag())) return;
//与上一条数据中的tag不同时,该显示bar了
if (!mBeans.get(position).getIndexTag().equals(mBeans.get(position - 1).getIndexTag())) {
outRect.set(0, dividerHeight, 0, 0);
}
}
}
}
package com.loaderman.contactsrecycledemo;
import android.app.Activity;
import android.content.Context;
import android.util.DisplayMetrics;
/**
* Created by MQ on 2016/12/16.
*/
public class DpUtil {
/**
* dp转换成px
*
* @param context Context
* @param dp dp
* @return px值
*/
public static float dp2px(Context context, float dp) {
final float scale = context.getResources().getDisplayMetrics().density;
return dp * scale + 0.5f;
}
/**
* sp转换成px
*
* @param context Context
* @param sp sp
* @return px值
*/
public static float sp2px(Context context, float sp) {
final float scale = context.getResources().getDisplayMetrics().scaledDensity;
return sp * scale;
}
/**
* 获得屏幕宽度
*
* @param context Context
* @return 屏幕宽度(像素)
*/
public static int getScreenSizeWidth(Context context) {
DisplayMetrics metric = new DisplayMetrics();
((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(metric);
return metric.widthPixels;
}
}
package com.loaderman.contactsrecycledemo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by MQ on 2017/5/18.
*/
public class IndexBar extends ViewGroup {
private int mHeight, mWidth;
private Context mContext;
private Paint mPaint;
private float centerY;
private String tag = "";
private boolean isShowTag;
private int position;
private final float circleRadius = 100;
public IndexBar(Context context) {
this(context, null);
}
public IndexBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public IndexBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init(attrs);
}
private void init(AttributeSet attrs) {
setWillNotDraw(false);
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mHeight = h;
mWidth = w;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(widthMeasureSpec, heightMeasureSpec);
//MeasureSpec封装了父View传给子View的布局要求
int wMode = MeasureSpec.getMode(widthMeasureSpec);
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int hMode = MeasureSpec.getMode(heightMeasureSpec);
int hSize = MeasureSpec.getSize(heightMeasureSpec);
switch (wMode) {
case MeasureSpec.EXACTLY:
mWidth = wSize;
break;
case MeasureSpec.AT_MOST:
mWidth = wSize;
break;
case MeasureSpec.UNSPECIFIED:
break;
}
switch (hMode) {
case MeasureSpec.EXACTLY:
mHeight = hSize;
break;
case MeasureSpec.AT_MOST:
mHeight = hSize;
break;
case MeasureSpec.UNSPECIFIED:
break;
}
setMeasuredDimension(mWidth, mHeight);
}
private int childWidth;
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childNum = getChildCount();
if (childNum <= 0) return;
//得到SideBar
View childView = getChildAt(0);
childWidth = childView.getMeasuredWidth();
//把SideBar排列到最右侧
childView.layout((mWidth - childWidth), 0, mWidth, mHeight);
}
/**
* @param centerY 要绘制的圆的Y坐标
* @param tag 要绘制的字母Tag
* @param position 字母Tag所在位置
*/
public void setDrawData(float centerY, String tag, int position) {
this.position = position;
this.centerY = centerY;
this.tag = tag;
isShowTag = true;
invalidate();
}
/**
* 通过标志位来控制是否来显示圆
*
* @param isShowTag 是否显示圆
*/
public void setTagStatus(boolean isShowTag) {
this.isShowTag = isShowTag;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isShowTag) {
//根据位置来不断变换Paint的颜色
ColorUtil.setPaintColor(mPaint, position);
//绘制圆和文字
canvas.drawCircle((mWidth - childWidth) / 2, centerY, circleRadius, mPaint);
mPaint.setColor(Color.WHITE);
mPaint.setTextSize(80);
canvas.drawText(tag, (mWidth - childWidth - mPaint.measureText(tag)) / 2, centerY - (mPaint.ascent() + mPaint.descent()) / 2, mPaint);
}
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return super.generateDefaultLayoutParams();
}
@Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return super.generateLayoutParams(p);
}
}
package com.loaderman.contactsrecycledemo;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* Created by MQ on 2017/5/17.
*/
public class SideBar extends View {
private int mHeight;
private int mWidth;
private Paint mPaint;
private int singleHeight;
private Context mContext;
private indexChangeListener listener;
private final int TOTAL_MARGIN = 160;
private final int TOP_MARGIN = 80;
public SideBar(Context context) {
this(context, null);
}
public SideBar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SideBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
init();
}
private void init() {
mPaint = new Paint();
mPaint.setDither(true);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.GRAY);
mPaint.setTextSize(35);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//导航栏居中显示,上下各有80dp的边距
mHeight = (int) (h - DpUtil.dp2px(mContext, TOTAL_MARGIN));
mWidth = w;
singleHeight = mHeight / indexStr.length();
}
@Override
protected void onDraw(Canvas canvas) {
for (int i = 0; i < indexStr.length(); i++) {
String textTag = indexStr.substring(i, i + 1);
float xPos = (mWidth - mPaint.measureText(textTag)) / 2;
canvas.drawText(textTag, xPos, singleHeight * (i + 1) + DpUtil.dp2px(mContext, TOP_MARGIN), mPaint);
}
}
private String indexStr = "ABCDEFGHIJKLMNOPQRSTUVWXY#";
public void setIndexStr(String indexStr) {
this.indexStr = indexStr;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//按下
mPaint.setColor(Color.BLACK);
invalidate();
case MotionEvent.ACTION_MOVE:
//滑动 event.getY()得到在父View中的Y坐标,通过和总高度的比例再乘以字符个数总长度得到按下的位置
int position = (int) ((event.getY() - getTop() - DpUtil.dp2px(mContext, 80)) / mHeight * indexStr.toCharArray().length);
if (position >= 0 && position < indexStr.length()) {
((IndexBar) getParent()).setDrawData(event.getY(), String.valueOf(indexStr.toCharArray()[position]), position);
if (listener != null) {
listener.indexChanged(indexStr.substring(position, position + 1));
}
}
break;
case MotionEvent.ACTION_UP:
//抬起
((IndexBar) getParent()).setTagStatus(false);
mPaint.setColor(Color.GRAY);
invalidate();
break;
}
return true;
}
public interface indexChangeListener {
void indexChanged(String tag);
}
public void setIndexChangeListener(indexChangeListener listener) {
this.listener = listener;
}
}
/*
* ******************************************************************************
* Copyright (c) 2014-2015 Gabriele Mariotti.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* *****************************************************************************
*/
package com.loaderman.contactsrecycledemo;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* @see android.support.v7.widget.RecyclerView#setItemAnimator(android.support.v7.widget.RecyclerView.ItemAnimator)
*/
public class SlideInOutLeftItemAnimator extends BaseItemAnimator {
public SlideInOutLeftItemAnimator(RecyclerView recyclerView) {
super(recyclerView);
}
protected void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
final View view = holder.itemView;
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
mRemoveAnimations.add(holder);
animation
.setDuration(getRemoveDuration())
.alpha(0)
.translationX(-mRecyclerView.getLayoutManager().getWidth())
.setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchRemoveStarting(holder);
}
@Override
public void onAnimationEnd(View view) {
animation.setListener(null);
ViewCompat.setAlpha(view, 1);
ViewCompat.setTranslationX(view, -mRecyclerView.getLayoutManager().getWidth());
dispatchRemoveFinished(holder);
mRemoveAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
@Override
protected void prepareAnimateAdd(RecyclerView.ViewHolder holder) {
ViewCompat.setTranslationX(holder.itemView, -mRecyclerView.getLayoutManager().getWidth());
}
protected void animateAddImpl(final RecyclerView.ViewHolder holder) {
final View view = holder.itemView;
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
mAddAnimations.add(holder);
animation.translationX(0)
.alpha(1)
.setDuration(getAddDuration())
.setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchAddStarting(holder);
}
@Override
public void onAnimationCancel(View view) {
ViewCompat.setTranslationX(view, 0);
ViewCompat.setAlpha(view, 1);
}
@Override
public void onAnimationEnd(View view) {
animation.setListener(null);
ViewCompat.setTranslationX(view, 0);
ViewCompat.setAlpha(view, 1);
dispatchAddFinished(holder);
mAddAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
}
package com.loaderman.contactsrecycledemo;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.graphics.drawable.shapes.RectShape;
import android.graphics.drawable.shapes.RoundRectShape;
/**
* @author amulya
* @datetime 14 Oct 2014, 3:53 PM
*/
public class TextDrawable extends ShapeDrawable {
private final Paint textPaint;
private final Paint borderPaint;
private static final float SHADE_FACTOR = 0.9f;
private final String text;
private final int color;
private final RectShape shape;
private final int height;
private final int width;
private final int fontSize;
private final float radius;
private final int borderThickness;
private TextDrawable(Builder builder) {
super(builder.shape);
// shape properties
shape = builder.shape;
height = builder.height;
width = builder.width;
radius = builder.radius;
// text and color
text = builder.toUpperCase ? builder.text.toUpperCase() : builder.text;
color = builder.color;
// text paint settings
fontSize = builder.fontSize;
textPaint = new Paint();
textPaint.setColor(builder.textColor);
textPaint.setAntiAlias(true);
textPaint.setFakeBoldText(builder.isBold);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTypeface(builder.font);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setStrokeWidth(builder.borderThickness);
// border paint settings
borderThickness = builder.borderThickness;
borderPaint = new Paint();
borderPaint.setColor(getDarkerShade(color));
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setStrokeWidth(borderThickness);
// drawable paint color
Paint paint = getPaint();
paint.setColor(color);
}
private int getDarkerShade(int color) {
return Color.rgb((int)(SHADE_FACTOR * Color.red(color)),
(int)(SHADE_FACTOR * Color.green(color)),
(int)(SHADE_FACTOR * Color.blue(color)));
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
Rect r = getBounds();
// draw border
if (borderThickness > 0) {
drawBorder(canvas);
}
int count = canvas.save();
canvas.translate(r.left, r.top);
// draw text
int width = this.width < 0 ? r.width() : this.width;
int height = this.height < 0 ? r.height() : this.height;
int fontSize = this.fontSize < 0 ? (Math.min(width, height) / 2) : this.fontSize;
textPaint.setTextSize(fontSize);
canvas.drawText(text, width / 2, height / 2 - ((textPaint.descent() + textPaint.ascent()) / 2), textPaint);
canvas.restoreToCount(count);
}
private void drawBorder(Canvas canvas) {
RectF rect = new RectF(getBounds());
rect.inset(borderThickness/2, borderThickness/2);
if (shape instanceof OvalShape) {
canvas.drawOval(rect, borderPaint);
}
else if (shape instanceof RoundRectShape) {
canvas.drawRoundRect(rect, radius, radius, borderPaint);
}
else {
canvas.drawRect(rect, borderPaint);
}
}
@Override
public void setAlpha(int alpha) {
textPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
textPaint.setColorFilter(cf);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public int getIntrinsicWidth() {
return width;
}
@Override
public int getIntrinsicHeight() {
return height;
}
public static IShapeBuilder builder() {
return new Builder();
}
public static class Builder implements IConfigBuilder, IShapeBuilder, IBuilder {
private String text;
private int color;
private int borderThickness;
private int width;
private int height;
private Typeface font;
private RectShape shape;
public int textColor;
private int fontSize;
private boolean isBold;
private boolean toUpperCase;
public float radius;
private Builder() {
text = "";
color = Color.GRAY;
textColor = Color.WHITE;
borderThickness = 0;
width = -1;
height = -1;
shape = new RectShape();
font = Typeface.create("sans-serif-light", Typeface.NORMAL);
fontSize = -1;
isBold = false;
toUpperCase = false;
}
public IConfigBuilder width(int width) {
this.width = width;
return this;
}
public IConfigBuilder height(int height) {
this.height = height;
return this;
}
public IConfigBuilder textColor(int color) {
this.textColor = color;
return this;
}
public IConfigBuilder withBorder(int thickness) {
this.borderThickness = thickness;
return this;
}
public IConfigBuilder useFont(Typeface font) {
this.font = font;
return this;
}
public IConfigBuilder fontSize(int size) {
this.fontSize = size;
return this;
}
public IConfigBuilder bold() {
this.isBold = true;
return this;
}
public IConfigBuilder toUpperCase() {
this.toUpperCase = true;
return this;
}
@Override
public IConfigBuilder beginConfig() {
return this;
}
@Override
public IShapeBuilder endConfig() {
return this;
}
@Override
public IBuilder rect() {
this.shape = new RectShape();
return this;
}
@Override
public IBuilder round() {
this.shape = new OvalShape();
return this;
}
@Override
public IBuilder roundRect(int radius) {
this.radius = radius;
float[] radii = {radius, radius, radius, radius, radius, radius, radius, radius};
this.shape = new RoundRectShape(radii, null, null);
return this;
}
@Override
public TextDrawable buildRect(String text, int color) {
rect();
return build(text, color);
}
@Override
public TextDrawable buildRoundRect(String text, int color, int radius) {
roundRect(radius);
return build(text, color);
}
@Override
public TextDrawable buildRound(String text, int color) {
round();
return build(text, color);
}
@Override
public TextDrawable build(String text, int color) {
this.color = color;
this.text = text;
return new TextDrawable(this);
}
}
public interface IConfigBuilder {
public IConfigBuilder width(int width);
public IConfigBuilder height(int height);
public IConfigBuilder textColor(int color);
public IConfigBuilder withBorder(int thickness);
public IConfigBuilder useFont(Typeface font);
public IConfigBuilder fontSize(int size);
public IConfigBuilder bold();
public IConfigBuilder toUpperCase();
public IShapeBuilder endConfig();
}
public static interface IBuilder {
public TextDrawable build(String text, int color);
}
public static interface IShapeBuilder {
public IConfigBuilder beginConfig();
public IBuilder rect();
public IBuilder round();
public IBuilder roundRect(int radius);
public TextDrawable buildRect(String text, int color);
public TextDrawable buildRoundRect(String text, int color, int radius);
public TextDrawable buildRound(String text, int color);
}
}
package com.loaderman.contactsrecycledemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private RecyclerView rl_recycle_view;
private ContactAdapter mAdapter;
private CustomItemDecoration decoration;
private SideBar side_bar;
List<ContactBean> nameList = new ArrayList<>();
private LinearLayoutManager layoutManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
side_bar.setIndexChangeListener(new SideBar.indexChangeListener() {
@Override
public void indexChanged(String tag) {
if (TextUtils.isEmpty(tag) || nameList.size() <= 0) return;
for (int i = 0; i < nameList.size(); i++) {
if (tag.equals(nameList.get(i).getIndexTag())) {
layoutManager.scrollToPositionWithOffset(i, 0);
// layoutManager.scrollToPosition(i);
return;
}
}
}
});
}
private void initViews() {
mAdapter = new ContactAdapter(this);
rl_recycle_view = (RecyclerView) findViewById(R.id.rl_recycle_view);
//侧边导航栏
side_bar = (SideBar) findViewById(R.id.side_bar);
layoutManager = new LinearLayoutManager(this);
rl_recycle_view.setLayoutManager(layoutManager);
rl_recycle_view.addItemDecoration(decoration = new CustomItemDecoration(this));
rl_recycle_view.setItemAnimator(new SlideInOutLeftItemAnimator(rl_recycle_view));
initDatas();
rl_recycle_view.setAdapter(mAdapter);
}
private void initDatas() {
String[] names = {"孙尚香", "安其拉", "白起", "不知火舞", "@小马快跑", "_德玛西亚之力_", "妲己", "狄仁杰", "典韦", "韩信",
"老夫子", "刘邦", "刘禅", "鲁班七号", "墨子", "孙膑", "孙尚香", "孙悟空", "项羽", "亚瑟",
"周瑜", "庄周", "蔡文姬", "甄姬", "廉颇", "程咬金", "后羿", "扁鹊", "钟无艳", "小乔", "王昭君", "虞姬",
"李元芳", "张飞", "刘备", "牛魔", "张良", "兰陵王", "露娜", "貂蝉", "达摩", "曹操", "芈月", "荆轲", "高渐离",
"钟馗", "花木兰", "关羽", "李白", "宫本武藏", "吕布", "嬴政", "娜可露露", "武则天", "赵云", "姜子牙",};
for (String name : names) {
ContactBean bean = new ContactBean();
bean.setName(name);
nameList.add(bean);
}
//对数据源进行排序
CommonUtil.sortData(nameList);
//返回一个包含所有Tag字母在内的字符串并赋值给tagsStr
String tagsStr = CommonUtil.getTags(nameList);
side_bar.setIndexStr(tagsStr);
decoration.setDatas(nameList, tagsStr);
mAdapter.addAll(nameList);
}
public void add(View view) {
ContactBean bean = new ContactBean();
bean.setName("安其拉666");
nameList.add(bean);
//对数据源进行排序
CommonUtil.sortData(nameList);
//返回一个包含所有Tag字母在内的字符串并赋值给tagsStr
String tagsStr = CommonUtil.getTags(nameList);
side_bar.setIndexStr(tagsStr);
decoration.setDatas(nameList, tagsStr);
//这里写死位置1 只是为了看动画效果
mAdapter.add(bean, 1);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.loaderman.contactsrecycledemo.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/rl_recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"/>
、
<com.loaderman.contactsrecycledemo.IndexBar
android:id="@+id/index_bar"
android:layout_width="200dp"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:background="@color/transparent">
<com.loaderman.contactsrecycledemo.SideBar
android:id="@+id/side_bar"
android:layout_width="30dp"
android:layout_height="match_parent"/>
</com.loaderman.contactsrecycledemo.IndexBar>
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:onClick="add"
android:text="ADD"/>
</RelativeLayout>
recycle_item_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_img"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="20dp"
android:contentDescription="@string/app_name"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginLeft="20dp"
android:gravity="center_vertical"
android:text="电话号码"
android:textColor="@color/black_deep"
android:textSize="16sp" />
</LinearLayout>
color.xml
<color name="white">#fff</color>
<color name="transparent">#00000000</color>
<color name="black_deep">#FF000000</color>
效果图:

原文:http://www.cnblogs.com/loaderman/p/7020555.html