Android实现Recycleview悬浮粘性头部外加右侧字母导航

如题,公司项目要实现这个效果:
Android实现Recycleview悬浮粘性头部外加右侧字母导航_第1张图片
Android实现Recycleview悬浮粘性头部外加右侧字母导航_第2张图片
图一是开始的画面,图二是滑动的画面,点击右侧字母需要滑动左侧到指定位置,然后左侧的顶部字母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 contacts   = new ArrayList<>();
                                    for (int i=0;i

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 implements StickyRecyclerHeadersAdapter {

    private List contacts;
    private static final String TAG = "ContactsAdapter";
    private ContactModel contact;

    public ContactsAdapter(List 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:




    

    
        

        
    

    

view_header.xml:





采用的第三方:

   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)