Android实现Recycleview悬浮粘性头部外加右侧字母导航
公司项目要实现这个效果:Android实现Recycleview悬浮粘性头部外加右侧字母导航
图一是开始的画面,图二是滑动的画面,点击右侧字母需要滑动左侧到指定位置,然后左侧的顶部字母A,B等需要悬浮。
实现思路:
右侧的联动可以用recycyeview中adapter的scrollToPositionWithOffset方法实现。左侧就是recycleview,后台返回的城市数据是这种类型的:
我进行了一层封装
1.建立实体类用来封装下标和城市名字:
public class ContactModel {
private String index;
private String name;
public ContactModel(String name){
this.index = NewFirstLetterUtil.getFirstLetter(name);
this.name = name;
}
public String getIndex() {
return index;
}
public String getName() {
return name;
}
}
2.讲服务器返回的数据进行封装:
List<ContactModel> contacts = new ArrayList<>();
for (int i=0;i<mTrainCityList.size();i++){
// ContactModel contactModel = new ContactModel(mTrainCityList.get(i).getCityName());
contacts.add(new ContactModel(mTrainCityList.get(i).getCityName()));
Collections.sort(contacts, new LetterComparator());
}
mContactModels.addAll(contacts);
mShowModels.addAll(mContactModels);
3.设置适配器
private void setNewAdapter() {
ContactsAdapter mAdapter = new ContactsAdapter(mShowModels);
mMainRecycleview.setLayoutManager(new LinearLayoutManager(this));
final StickyRecyclerHeadersDecoration headersDecor = new StickyRecyclerHeadersDecoration(mAdapter);
mMainRecycleview.addItemDecoration(headersDecor);
mAdapter.setOnItemClickListtener(new ContactsAdapter.OnItemClickListtener() {
@Override
public void onItemClick(int pos) {
// Toast.makeText(TrainNewStartActivity.this,"惦记的pos:"+pos+"数据:"+mShowModels.get(pos).getName(),Toast.LENGTH_SHORT).show();
Intent intent = new Intent();
intent.putExtra("data", mShowModels.get(pos).getName());
Log.d("lwp","data:"+mShowModels.get(pos).getName());
setResult(RESULT_OK, intent);
finish();
}
});
mMainRecycleview.setAdapter(mAdapter);
mMain_side_bar.setLazyRespond(false);
// 侧边设置相关
mMain_side_bar.setOnSelectIndexItemListener(new WaveSideBarView.OnSelectIndexItemListener() {
@Override
public void onSelectIndexItem(String letter) {
for (int i = 0; i< mContactModels.size(); i++) {
if (mContactModels.get(i).getIndex().equals(letter)) {
((LinearLayoutManager) mMainRecycleview.getLayoutManager()).scrollToPositionWithOffset(i, 0);
return;
}
}
}
});
}
4.适配器代码:
public class ContactsAdapter extends RecyclerView.Adapter<ContactsAdapter.ContactsViewHolder> implements StickyRecyclerHeadersAdapter {
private List<ContactModel> contacts;
private static final String TAG = "ContactsAdapter";
private ContactModel contact;
public ContactsAdapter(List<ContactModel> contacts) {
this.contacts = contacts;
}
@Override
public ContactsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.layaout_item_contacts, null);
return new ContactsViewHolder(view);
}
@Override
public void onBindViewHolder(ContactsViewHolder holder, final int position) {
contact = contacts.get(position);
Log.e(TAG, "onBindViewHolder: index:" + contact.getIndex());
if (position == 0 || !contacts.get(position-1).getIndex().equals(contact.getIndex())) {
holder.tvIndex.setVisibility(View.GONE);
holder.tvIndex.setText(contact.getIndex());
} else {
holder.tvIndex.setVisibility(View.GONE);
}
holder.tvName.setText(contact.getName());
holder.tvName.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("lwp","惦记的pos:"+position);
onItemClickListtener.onItemClick(position);
}
});
}
public interface OnItemClickListtener{
void onItemClick(int pos);
}
public OnItemClickListtener onItemClickListtener;
public void setOnItemClickListtener(OnItemClickListtener onItemClickListtener) {
this.onItemClickListtener = onItemClickListtener;
}
@Override
public long getHeaderId(int position) {
if (contacts.get(position).getIndex().equals("A")){
return 0;
}else if (contacts.get(position).getIndex().equals("B")){
return 1;
}else if (contacts.get(position).getIndex().equals("C")){
return 2;
}else if (contacts.get(position).getIndex().equals("D")){
return 3;
}else if (contacts.get(position).getIndex().equals("E")){
return 4;
}else if (contacts.get(position).getIndex().equals("F")){
return 5;
}else if (contacts.get(position).getIndex().equals("G")){
return 6;
}else if (contacts.get(position).getIndex().equals("H")){
return 7;
}else if (contacts.get(position).getIndex().equals("I")){
return 8;
}else if (contacts.get(position).getIndex().equals("J")){
return 9;
}else if (contacts.get(position).getIndex().equals("K")){
return 10;
}else if (contacts.get(position).getIndex().equals("L")){
return 11;
}else if (contacts.get(position).getIndex().equals("M")){
return 12;
}else if (contacts.get(position).getIndex().equals("N")){
return 13;
}else if (contacts.get(position).getIndex().equals("O")){
return 14;
}else if (contacts.get(position).getIndex().equals("P")){
return 15;
}else if (contacts.get(position).getIndex().equals("Q")){
return 16;
}else if (contacts.get(position).getIndex().equals("R")){
return 17;
}else if (contacts.get(position).getIndex().equals("S")){
return 18;
}else if (contacts.get(position).getIndex().equals("T")){
return 19;
}else if (contacts.get(position).getIndex().equals("U")){
return 20;
}else if (contacts.get(position).getIndex().equals("V")){
return 21;
}else if (contacts.get(position).getIndex().equals("Y")){
return 22;
}else if (contacts.get(position).getIndex().equals("X")){
return 23;
}else if (contacts.get(position).getIndex().equals("Y")){
return 24;
}else if (contacts.get(position).getIndex().equals("Z")){
return 25;
}else {
return -1;
}
}
@Override
public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.view_header, parent, false);
return new RecyclerView.ViewHolder(view) {
};
}
@Override
public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder, int position) {
TextView textView = (TextView) holder.itemView;
textView.setText(String.valueOf(contacts.get(position).getIndex()));
}
@Override
public int getItemCount() {
return contacts.size();
}
class ContactsViewHolder extends RecyclerView.ViewHolder {
TextView tvIndex;
ImageView ivAvatar;
TextView tvName;
ContactsViewHolder(View itemView) {
super(itemView);
tvIndex = (TextView) itemView.findViewById(R.id.tv_index);
ivAvatar = (ImageView) itemView.findViewById(R.id.iv_avatar);
tvName = (TextView) itemView.findViewById(R.id.tv_name);
}
}
}
5.两个布局文件:
layaout_item_contacts.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_index"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="12dp"
android:text="A"
android:textSize="14sp"
android:background="#E0E0E0"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:id="@+id/iv_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:visibility="gone"
android:layout_margin="10dp"
/>
<TextView
android:id="@+id/tv_name"
android:layout_marginLeft="12dp"
android:layout_width="wrap_content"
android:layout_height="34dp"
android:layout_toRightOf="@+id/iv_avatar"
android:text="南尘"
android:gravity="center_vertical"
android:textColor="#424242"
android:textSize="16sp"
android:layout_centerVertical="true" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="15dp"
android:background="#e8e8e8" />
</LinearLayout>
view_header.xml:
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="12dp"
android:textSize="14sp"
android:textStyle="bold"
android:background="#E0E0E0"
tools:text="Animals starting with A"
/>
采用的第三方:
compile 'com.github.nanchen2251:WaveSideBar:1.0.6'
compile 'com.timehop.stickyheadersrecyclerview:library:0.4.3@aar'
右侧字母用的是wavesidebar,但是由于不太符合设计图,所有我没有用他的,而是自己拿过来重新定义了(该类没提供修改,建议完善),如下:
public class SlfWaveSlideBarView extends View {
private final static int DEFAULT_TEXT_SIZE = 14; // sp
private final static int DEFAULT_MAX_OFFSET = 80; //dp
private final static String[] DEFAULT_INDEX_ITEMS = {"A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};
private String[] mIndexItems;
/**
* the index in {@link #mIndexItems} of the current selected index item,
* it's reset to -1 when the finger up
*/
private int mCurrentIndex = -1;
/**
* Y coordinate of the point where finger is touching,
* the baseline is top of {@link #mStartTouchingArea}
* it's reset to -1 when the finger up
*/
private float mCurrentY = -1;
private Paint mPaint;
private int mTextColor;
private float mTextSize;
/**
* the height of each index item
*/
private float mIndexItemHeight;
/**
* offset of the current selected index item
*/
private float mMaxOffset;
/**
* {@link #mStartTouching} will be set to true when {@link MotionEvent#ACTION_DOWN}
* happens in this area, and the side bar should start working.
*/
private RectF mStartTouchingArea = new RectF();
/**
* height and width of {@link #mStartTouchingArea}
*/
private float mBarHeight;
private float mBarWidth;
/**
* Flag that the finger is starting touching.
* If true, it means the {@link MotionEvent#ACTION_DOWN} happened but
* {@link MotionEvent#ACTION_UP} not yet.
*/
private boolean mStartTouching = false;
/**
* if true, the {@link WaveSideBarView.OnSelectIndexItemListener#onSelectIndexItem(String)}
* will not be called until the finger up.
* if false, it will be called when the finger down, up and move.
*/
private boolean mLazyRespond = false;
/**
* the position of the side bar, default is {@link #POSITION_RIGHT}.
* You can set it to {@link #POSITION_LEFT} for people who use phone with left hand.
*/
private int mSideBarPosition;
public static final int POSITION_RIGHT = 0;
public static final int POSITION_LEFT = 1;
/**
* the alignment of items, default is {@link #TEXT_ALIGN_CENTER}.
*/
private int mTextAlignment;
public static final int TEXT_ALIGN_CENTER = 0;
public static final int TEXT_ALIGN_LEFT = 1;
public static final int TEXT_ALIGN_RIGHT = 2;
/**
* observe the current selected index item
*/
private WaveSideBarView.OnSelectIndexItemListener onSelectIndexItemListener;
/**
* the baseline of the first index item text to draw
*/
private float mFirstItemBaseLineY;
/**
* for {@link #dp2px(int)} and {@link #sp2px(int)}
*/
private DisplayMetrics mDisplayMetrics;
public SlfWaveSlideBarView(Context context) {
this(context, null);
}
public SlfWaveSlideBarView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SlfWaveSlideBarView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mDisplayMetrics = context.getResources().getDisplayMetrics();
TypedArray typedArray = context.obtainStyledAttributes(attrs, com.nanchen.wavesidebar.R.styleable.WaveSideBarView);
mLazyRespond = typedArray.getBoolean(com.nanchen.wavesidebar.R.styleable.WaveSideBarView_sidebar_lazy_respond, false);
mTextColor = typedArray.getColor(com.nanchen.wavesidebar.R.styleable.WaveSideBarView_sidebar_text_color, Color.GRAY);
mMaxOffset = typedArray.getDimension(com.nanchen.wavesidebar.R.styleable.WaveSideBarView_sidebar_max_offset, dp2px(DEFAULT_MAX_OFFSET));
mSideBarPosition = typedArray.getInt(com.nanchen.wavesidebar.R.styleable.WaveSideBarView_sidebar_position, POSITION_RIGHT);
mTextAlignment = typedArray.getInt(com.nanchen.wavesidebar.R.styleable.WaveSideBarView_sidebar_text_alignment, TEXT_ALIGN_CENTER);
typedArray.recycle();
mTextSize = sp2px(DEFAULT_TEXT_SIZE);
mIndexItems = DEFAULT_INDEX_ITEMS;
initPaint();
}
private void initPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(mTextColor);
mPaint.setTextSize(mTextSize);
switch (mTextAlignment) {
case TEXT_ALIGN_CENTER: mPaint.setTextAlign(Paint.Align.CENTER); break;
case TEXT_ALIGN_LEFT: mPaint.setTextAlign(Paint.Align.LEFT); break;
case TEXT_ALIGN_RIGHT: mPaint.setTextAlign(Paint.Align.RIGHT); break;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
mIndexItemHeight = fontMetrics.bottom - fontMetrics.top;
mBarHeight = mIndexItems.length * mIndexItemHeight;
// calculate the width of the longest text as the width of side bar
for (String indexItem : mIndexItems) {
mBarWidth = Math.max(mBarWidth, mPaint.measureText(indexItem));
}
float areaLeft = (mSideBarPosition == POSITION_LEFT) ? 0 : (width - mBarWidth - getPaddingRight());
float areaRight = (mSideBarPosition == POSITION_LEFT) ? (getPaddingLeft() + areaLeft + mBarWidth) : width;
float areaTop = height/2 - mBarHeight/2;
float areaBottom = areaTop + mBarHeight;
mStartTouchingArea.set(
areaLeft,
areaTop,
areaRight,
areaBottom);
// the baseline Y of the first item' text to draw
mFirstItemBaseLineY = (height/2 - mIndexItems.length*mIndexItemHeight/2)
+ (mIndexItemHeight/2 - (fontMetrics.descent-fontMetrics.ascent)/2)
- fontMetrics.ascent;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// draw each item
for (int i = 0, mIndexItemsLength = mIndexItems.length; i < mIndexItemsLength; i++) {
float baseLineY = mFirstItemBaseLineY + mIndexItemHeight*i;
// calculate the scale factor of the item to draw
float scale = getItemScale(i);
int alphaScale = (i == mCurrentIndex) ? (255) : (int) (255 * (1-scale));
mPaint.setAlpha(alphaScale);
mPaint.setTextSize(mTextSize + mTextSize*scale);
float baseLineX = 0f;
if (mSideBarPosition == POSITION_LEFT) {
switch (mTextAlignment) {
case TEXT_ALIGN_CENTER:
baseLineX = getPaddingLeft() + mBarWidth/2 + mMaxOffset*scale;
break;
case TEXT_ALIGN_LEFT:
baseLineX = getPaddingLeft() + mMaxOffset*scale;
break;
case TEXT_ALIGN_RIGHT:
baseLineX = getPaddingLeft() + mBarWidth + mMaxOffset*scale;
break;
}
} else {
switch (mTextAlignment) {
case TEXT_ALIGN_CENTER:
baseLineX = getWidth() - getPaddingRight() - mBarWidth/2 - mMaxOffset*scale;
break;
case TEXT_ALIGN_RIGHT:
baseLineX = getWidth() - getPaddingRight() - mMaxOffset*scale;
break;
case TEXT_ALIGN_LEFT:
baseLineX = getWidth() - getPaddingRight() - mBarWidth - mMaxOffset*scale;
break;
}
}
// draw
canvas.drawText(
mIndexItems[i], //item text to draw
baseLineX, //baseLine X
baseLineY, // baseLine Y
mPaint);
}
// reset paint
mPaint.setAlpha(255);
mPaint.setTextSize(mTextSize);
}
/**
* calculate the scale factor of the item to draw
*
* @param index the index of the item in array {@link #mIndexItems}
* @return the scale factor of the item to draw
*/
private float getItemScale(int index) {
float scale = 0;
if (mCurrentIndex != -1) {
float distance = Math.abs(mCurrentY - (mIndexItemHeight*index+mIndexItemHeight/2)) / mIndexItemHeight;
scale = 1 - distance*distance/16;
scale = Math.max(scale, 0);
}
return scale;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mIndexItems.length == 0) {
return super.onTouchEvent(event);
}
float eventY = event.getY();
float eventX = event.getX();
mCurrentIndex = getSelectedIndex(eventY);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mStartTouchingArea.contains(eventX, eventY)) {
mStartTouching = true;
if (!mLazyRespond && onSelectIndexItemListener != null) {
onSelectIndexItemListener.onSelectIndexItem(mIndexItems[mCurrentIndex]);
}
// invalidate();
return true;
} else {
mCurrentIndex = -1;
return false;
}
case MotionEvent.ACTION_MOVE:
if (mStartTouching && !mLazyRespond && onSelectIndexItemListener != null) {
onSelectIndexItemListener.onSelectIndexItem(mIndexItems[mCurrentIndex]);
}
// invalidate();
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mLazyRespond && onSelectIndexItemListener != null) {
onSelectIndexItemListener.onSelectIndexItem(mIndexItems[mCurrentIndex]);
}
mCurrentIndex = -1;
mStartTouching = false;
// invalidate();
return true;
}
return super.onTouchEvent(event);
}
private int getSelectedIndex(float eventY) {
mCurrentY = eventY - (getHeight()/2 - mBarHeight /2);
if (mCurrentY <= 0) {
return 0;
}
int index = (int) (mCurrentY / this.mIndexItemHeight);
if (index >= this.mIndexItems.length) {
index = this.mIndexItems.length - 1;
}
return index;
}
private float dp2px(int dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, this.mDisplayMetrics);
}
private float sp2px(int sp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, this.mDisplayMetrics);
}
public void setIndexItems(String... indexItems) {
mIndexItems = Arrays.copyOf(indexItems, indexItems.length);
requestLayout();
}
public void setTextColor(int color) {
mTextColor = color;
mPaint.setColor(color);
invalidate();
}
public void setPosition(int position) {
if (position != POSITION_RIGHT && position != POSITION_LEFT) {
throw new IllegalArgumentException("the position must be POSITION_RIGHT or POSITION_LEFT");
}
mSideBarPosition = position;
requestLayout();
}
public void setMaxOffset(int offset) {
mMaxOffset = offset;
invalidate();
}
public void setLazyRespond(boolean lazyRespond) {
mLazyRespond = lazyRespond;
}
public void setTextAlign(int align) {
if (mTextAlignment == align) {
return;
}
switch (align) {
case TEXT_ALIGN_CENTER: mPaint.setTextAlign(Paint.Align.CENTER); break;
case TEXT_ALIGN_LEFT: mPaint.setTextAlign(Paint.Align.LEFT); break;
case TEXT_ALIGN_RIGHT: mPaint.setTextAlign(Paint.Align.RIGHT); break;
default:
throw new IllegalArgumentException(
"the alignment must be TEXT_ALIGN_CENTER, TEXT_ALIGN_LEFT or TEXT_ALIGN_RIGHT");
}
mTextAlignment = align;
invalidate();
}
public void setOnSelectIndexItemListener(WaveSideBarView.OnSelectIndexItemListener onSelectIndexItemListener) {
this.onSelectIndexItemListener = onSelectIndexItemListener;
}
public interface OnSelectIndexItemListener {
void onSelectIndexItem(String letter);
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
以上是 Android实现Recycleview悬浮粘性头部外加右侧字母导航 的全部内容, 来源链接: utcz.com/p/241895.html