Android实现Recycleview悬浮粘性头部外加右侧字母导航
公司项目要实现这个效果:Android实现Recycleview悬浮粘性头部外加右侧字母导航
图一是开始的画面,图二是滑动的画面,点击右侧字母需要滑动左侧到指定位置,然后左侧的顶部字母A,B等需要悬浮。
实现思路:
右侧的联动可以用recycyeview中adapter的scrollToPositionWithOffset方法实现。左侧就是recycleview,后台返回的城市数据是这种类型的:
{"returnCode":1,"returnMsg":"操作成功","data":[{"startWord":"A","trainCityList":[{"cityId":531,"cityName":"昂昂溪","code":null},{"cityId":2137,
我进行了一层封装
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); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。
赞 (0)