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