<android>视频流横向列表左滑加载更多,松手跳转页面,自定义view,RecyclerView,Horizontal,文字竖向居中排列

本文介绍如何通过自定义HorizontalMoreDataView和HorizontalMoreBaseAdapter,实现RecyclerView的左滑加载更多功能,包括视图动画、滑动响应和可跳转逻辑。通过设置自定义属性,提供了灵活的用户体验和界面控制。
先上效果:


整体思路很简单,利用列表上拉加载更多的原理,图中“左划加载”的view作为Adapter最后一个itemView,我们将自定义这个itemView, 根据recyclerView的左滑距离来操作这个自定义view, 然后再封装一下BaseAdapter,让子Adapter继承并实现它的方法即可。也可以通过自定义属性moreViewHideMode=true隐藏这个view, 就会有从外侧拉进来的效果。不足之处后期会及时更新。

HorizontalMoreDataView:

public class HorizontalMoreDataView extends View {
    private Paint paint;
    private int mCenterY;
    private int mCenterX;
    int x = 0;
    private Paint fontPaint;
    int fontSize = 35;
    float fontHeight = 0;
    private float offset;
    String title = "左划加载";
    String jumpTitle = "松开跳转";
    String content = "";
    private int diverWidth;
    private int dampingNum = 80;
    private int changeNum = 5;
    private int mWidth;
    private int viewWidth = -1;
    private boolean hideMode;
    private char[] chars;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            try {
                ViewGroup.LayoutParams layoutParams = getLayoutParams();
                layoutParams.width = mWidth;
                setLayoutParams(layoutParams);
            } catch (Exception e) {
            }
        }
    };

    public HorizontalMoreDataView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public HorizontalMoreDataView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }


    /**
     * 初始化
     *
     * @param context
     * @param attrs
     * @param defStyleAttr
     */
    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.HorizontalMoreDataView, defStyleAttr, 0);
        fontSize = typedArray.getLayoutDimension(R.styleable.HorizontalMoreDataView_moreViewTextSize, -1);
        title = typedArray.getString(R.styleable.HorizontalMoreDataView_moreViewText);
        jumpTitle = typedArray.getString(R.styleable.HorizontalMoreDataView_moreViewJumpText);
        diverWidth = typedArray.getLayoutDimension(R.styleable.HorizontalMoreDataView_moreViewDiverWidth, 5);
        hideMode = typedArray.getBoolean(R.styleable.HorizontalMoreDataView_moreViewHideMode, false);
        content = title;
        fontHeight = fontSize / 1.94f;
        typedArray.recycle();

        paint = new Paint();
        paint.setStrokeWidth(diverWidth);
        paint.setColor(Color.parseColor("#333333"));
        paint.setStyle(Paint.Style.STROKE);
        paint.setAntiAlias(true);
        paint.setDither(true);

        fontPaint = new Paint();
        fontPaint.setStrokeWidth(7);
        fontPaint.setColor(Color.parseColor("#333333"));
        fontPaint.setTextSize(fontSize);
        fontPaint.setAntiAlias(true);
        fontPaint.setDither(true);
        offset = (fontPaint.getFontMetrics().top + fontPaint.getFontMetrics().bottom) / 2; //文字偏移量4
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (hideMode) {
            width = 0;
            hideMode = false;
        }
        if (viewWidth == -1)
            viewWidth = width;
        setMeasuredDimension(width, height);
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mCenterY = getHeight() / 2;
        mCenterX = getWidth() / 2;

        Path path = new Path();
        path.moveTo(mCenterX / 1.6F, mCenterY - mCenterY / 3);
        path.lineTo(mCenterX / 1.6F - x, mCenterY);
        path.lineTo(mCenterX / 1.6F, mCenterY + mCenterY / 3);
        canvas.drawPath(path, paint);
        chars = content.toCharArray();

        //文字竖向居中排布处理
        for (int i = 0; i < chars.length; i++) {
            if (chars.length % 2 == 0) { //偶数索引
                if (i == (chars.length / 2 - 1)) //中间位置的 靠上第一个
                    canvas.drawText(chars[i] + "", mCenterX, mCenterY - (fontSize * 1.4F) / 2 - offset, fontPaint);

                if (i == (chars.length / 2)) //中间位置的 靠下第一个
                    canvas.drawText(chars[i] + "", mCenterX, mCenterY + (fontSize * 1.4F) / 2 - offset, fontPaint);

                if (i < (chars.length / 2 - 1)) { // 靠上第一个的 上边的n个
                    int num = Math.abs((chars.length / 2 - 1) - i); //间隔个数
                    canvas.drawText(chars[i] + "", mCenterX, mCenterY - (num * ((fontSize * 1.4F)) + ((fontSize * 1.4F)) / 2) - offset, fontPaint);
                }

                if (i > (chars.length / 2)) {  //靠下第一个的 下边的n个
                    int num = Math.abs(i - (chars.length / 2));
                    canvas.drawText(chars[i] + "", mCenterX, mCenterY + (num * ((fontSize * 1.4F)) + ((fontSize * 1.4F)) / 2) - offset, fontPaint);
                }

            } else { //奇数索引

                if (i == ((chars.length - 1) / 2)) //中间位置
                    canvas.drawText(chars[i] + "", mCenterX, mCenterY - offset, fontPaint);

                if (i < ((chars.length - 1) / 2)) { //中间以上的
                    int num = Math.abs(i - (chars.length / 2));
                    canvas.drawText(chars[i] + "", mCenterX, mCenterY - (num * ((fontSize * 1.4F))) - offset, fontPaint);
                }

                if (i > ((chars.length - 1) / 2)) { //中间以下的
                    int num = Math.abs(i - (chars.length / 2));
                    canvas.drawText(chars[i] + "", mCenterX, mCenterY + (num * ((fontSize * 1.4F))) - offset, fontPaint);
                }

            }

        }

    }


    /**
     * 设置移动距离
     *
     * @param distance
     */
    public void setSlipDistance(int distance) {
        x = distance / dampingNum;
        if (x > changeNum) {
            content = jumpTitle;
        } else {
            content = title;
        }
        invalidate();
        mWidth = viewWidth + ((distance - 50) / 5);
        ViewGroup.LayoutParams layoutParams = getLayoutParams();
        layoutParams.width = mWidth;
        setLayoutParams(layoutParams);
    }

    /**
     * 是否到达可跳转的阀值
     * @return
     */
    public boolean getCanJump() {
        return x > changeNum;
    }


    /**
     * 手指释放 恢复view
     */
    public void release() {
        x = 0;
        content = title;
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (mWidth >= viewWidth) {
                    try {
                        Thread.sleep(1);
                        mWidth = mWidth - 6;
                        handler.sendEmptyMessage(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        invalidate();
    }

}

HorizontalMoreBaseAdapter:

public abstract class HorizontalMoreBaseAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    public HorizontalMoreDataView horizontalView;
    public static int MOREDATAVIEWTYPE = 9000;
    private float x1;
    private float y1;
    private float x2;
    private float y2;

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == 9000) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.horizontal_more_view_footer, null);
            return new HorizontalMoreDataViewViewHolder(view);
        } else {
            return HorizontalMoreOnCreateViewHolder(parent, viewType);
        }
    }


    @Override
    public int getItemViewType(int position) {
        if (position == getItemCount() - 1) {
            return MOREDATAVIEWTYPE;
        } else {
            return HorizontalMoreGetItemViewType(position);
        }
    }


    class HorizontalMoreDataViewViewHolder extends RecyclerView.ViewHolder {
        public HorizontalMoreDataViewViewHolder(@NonNull View itemView) {
            super(itemView);
            horizontalView = (HorizontalMoreDataView) itemView.findViewById(R.id.horzonView);
        }
    }


    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        recyclerView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN) {//当手指按下的时候
                    x1 = event.getX();
                    y1 = event.getY();
                }
                if (event.getAction() == MotionEvent.ACTION_MOVE) {//当手指移动的时候
                    x2 = event.getX();
                    y2 = event.getY();
                    //45度分割 横向滑动
                    if (Math.abs(x2 - x1) > 50 && Math.abs(x2 - x1) > Math.abs(y2 - y1)) {
                        if (horizontalView != null)
                            horizontalView.setSlipDistance((int) (Math.abs(x1 - x2)));
                    }
                    //纵向滑动
                    if (Math.abs(y2 - y1) > 50 && Math.abs(y2 - y1) > Math.abs(x2 - x1)) {
                    }
                }
                if (event.getAction() == MotionEvent.ACTION_UP) {
                    x1 = 0;
                    x2 = 0;
                    if (horizontalView != null) {
                        if (horizontalView.getCanJump() && relaseJumpActivityListener != null)
                            relaseJumpActivityListener.relaseJump();
                        horizontalView.release();
                    }

                }
                return false;
            }

        });
    }


    public abstract RecyclerView.ViewHolder HorizontalMoreOnCreateViewHolder(@NonNull ViewGroup parent, int viewType);
    public abstract int HorizontalMoreGetItemViewType(int postion);
    RelaseJumpActivityListener relaseJumpActivityListener;

    public void setRelaseJumpActivityListener(RelaseJumpActivityListener relaseJumpActivityListener) {
        this.relaseJumpActivityListener = relaseJumpActivityListener;
    }

    public interface RelaseJumpActivityListener {
        void relaseJump();
    }
}
horizontal_more_view_footer(xml):
<com.xxx.xx.utils.HorizontalMoreDataView
    android:background="#fff"
    app:moreViewTextSize="16sp"
    app:moreViewText="左划加载"
    app:moreViewJumpText="松开跳转"
    app:moreViewDiverWidth="5"
    app:moreViewHideMode="false"
    android:id="@+id/horzonView"
    android:layout_width="55dp"
    android:layout_height="wrap_content">

</com.watcn.wat.utils.HorizontalMoreDataView>

自定义属性:

<declare-styleable name="HorizontalMoreDataView">
    <attr name="moreViewTextSize" format="dimension"/>
    <attr name="moreViewText" format="string"/>
    <attr name="moreViewJumpText" format="string"/>
    <attr name="moreViewDiverWidth" format="integer"/>
    <attr name="moreViewHideMode" format="boolean"/>
</declare-styleable>
MainActivity(使用方法):
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(linearLayoutManager);
MyAdapter   myAdapter = new MyAdapter();
recyclerView.setAdapter(myAdapter);
myAdapter.setRelaseJumpActivityListener(new HorizontalMoreBaseAdapter.RelaseJumpActivityListener() {
    @Override
    public void relaseJump() {
        Toast.showToast("跳转新Activity");
    }
});

adapter继承HorizontalMoreBaseAdapter,实现HorizontalMoreOnCreateViewHolder和HorizontalMoreGetItemViewType,按照adapter的写法继续写就可以了。当然有很多的属性也应该提供出来,后期完善会及时更新。不足之处还请大佬们多多指教。

<think>好的,我现在需要帮助用户实现一个自定义横向RecyclerView列表,具备加载更多松手跳转页面功能,并且文字竖向居中排列。首先,我得理清楚用户的具体需求,然后逐步思考如何实现这些功能。 用户提到的是横向RecyclerView,所以需要设置布局管理器为水平方向。然后加载更多,这可能涉及到动监听,当用户向动到最后一个项时触发加载更多。不过用户还提到松手跳转页面,这可能是指在动到某个位置后释放,跳转到另一个页面。这里可能需要区分两种手势:一种是动到边缘加载更多,另一种是动某个item后松手跳转。 首先,处理RecyclerView横向布局。使用LinearLayoutManager设置水平方向即可。然后,文字竖向居中,可能是指每个item中的文本垂直居中,这可以通过在item的布局文件中设置TextView的gravity为center_vertical,或者使用ConstraintLayout来居中。 接下来是加载更多。通常加载更多是在滚动到底部时触发,但这里是横向,所以应该是在滚动到最右端时触发。可能需要添加一个OnScrollListener,检测是否已经滚动到最后一个可见的item,然后加载更多数据。不过用户提到的是“加载更多”,可能是指通过动手势触发,而不是滚动到底部自动加载。这里可能需要结合手势处理,比如当用户向动到一定阈值时,显示加载更多的提示,释放后执行加载操作。但如果是横向滚动,可能需要使用ItemTouchHelper来处理动事件,或者自定义手势检测。 然后是松手跳转页面,可能是指在动某个item到一定位置后,释放手指会触发页面跳转。这可能需要检测动的位置,当动超过某个阈值时,触发跳转。这里可能需要自定义一个ItemTouchHelper.SimpleCallback,处理动事件,并在onSwiped方法中判断动方向,当到一定程度时跳转页面。但用户的需求可能是在动整个RecyclerView时触发,而不是单个item的动,这可能需要区分处理。 另外,文字竖向居中排列,需要确保每个item中的TextView在垂直方向上居中。这可以通过设置布局属性,比如android:gravity="center_vertical"或者使用ConstraintLayout的约束条件。 可能还需要考虑自定义ItemDecoration来调整item的布局,或者处理触摸事件来检测动的距离。例如,当用户RecyclerView时,如果已经到达最右端,继续动会触发加载更多,此时可能需要显示一个加载中的提示,松手加载数据。同时,如果在某个item上动并释放,可能需要跳转到另一个页面,这需要判断动的item的位置以及动的距离。 现在需要整合这些思路,分步骤实现: 1. 创建横向RecyclerView: - 设置LinearLayoutManager为水平方向。 - 设置Adapter,并在item布局中确保文字垂直居中。 2. 实现加载更多: - 添加OnScrollListener,检测是否到最右端,触发加载更多。 - 或者通过手势检测,当用户到一定距离时显示加载提示,松手加载数据。 3. 松手跳转页面: - 可能需要在某个item动时触发,使用ItemTouchHelper检测动动作。 - 当动超过阈值时,执行跳转逻辑。 4. 文字垂直居中: - 在item的布局文件中设置TextView的垂直居中。 可能需要处理动冲突,比如RecyclerView横向滚动和item的动操作之间的冲突。例如,如果使用ItemTouchHelper来处理item的动,可能会影响RecyclerView本身的滚动,需要合理设置。 另外,加载更多的逻辑可能需要在下拉刷新或者动到底部时触发,但这里是,可能需要自定义手势处理。例如,当用户向RecyclerView并且已经处于最后一个item时,显示加载更多,释放后加载。 对于松手跳转页面,可能是指动某个item到边后松手跳转到详情页面。这时候需要使用ItemTouchHelper来处理每个item的动,当超过一定阈值时,触发跳转。 总结实现步骤: - 布局设置:RecyclerView横向,item文字垂直居中。 - 加载更多:检测滚动到最右端,或者通过手势触发。 - 跳转页面:处理item的动事件,超过阈值后跳转。 可能需要分开处理两种手势:RecyclerView的整体动和item的单独动。这可能会比较复杂,需要仔细设计触摸事件的分发和处理。 现在参考用户提供的引用,其中提到移动应用中使用动动画来过渡页面,可能可以借鉴这种思路,当用户到一定位置,显示一个提示,松手后执行跳转,并使用平的动画过渡。 最后,编写代码示例,包括RecyclerView的配置、自定义Adapter、加载更多跳转逻辑的实现,以及文字居中的处理。</think>### 实现步骤及代码示例 #### 一、横向RecyclerView基础配置 1. 在XML布局中定义RecyclerView: ```xml <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" tools:listitem="@layout/item_horizontal"/> ``` 2. 创建Adapter(关键代码): ```kotlin class HorizontalAdapter : RecyclerView.Adapter<HorizontalAdapter.ViewHolder>() { inner class ViewHolder(val binding: ItemHorizontalBinding) : RecyclerView.ViewHolder(binding.root) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val binding = ItemHorizontalBinding.inflate(LayoutInflater.from(parent.context), parent, false) return ViewHolder(binding) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.binding.textView.text = "Item $position" } override fun getItemCount() = 20 } ``` #### 二、文字垂直居中实现 在`item_horizontal.xml`中: ```xml <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="100dp" android:layout_height="match_parent"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_vertical" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> ``` #### 三、加载更多实现 ```kotlin recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { val layoutManager = recyclerView.layoutManager as LinearLayoutManager val lastVisiblePosition = layoutManager.findLastVisibleItemPosition() if (lastVisiblePosition == adapter.itemCount - 1) { // 触发加载更多逻辑 loadMoreData() } } }) private fun loadMoreData() { // 模拟网络请求 Handler(Looper.getMainLooper()).postDelayed({ adapter.notifyItemRangeInserted(adapter.itemCount, 5) }, 1000) } ``` #### 四、松手跳转页面功能 使用ItemTouchHelper实现动监听: ```kotlin val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback( 0, ItemTouchHelper.LEFT) { override fun onMove(...) = false override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { if (direction == ItemTouchHelper.LEFT) { val context = viewHolder.itemView.context context.startActivity(Intent(context, DetailActivity::class.java)) // 添加页面切换动画 (context as Activity).overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left) } } override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float { return 0.5f // 设置动阈值 } }) itemTouchHelper.attachToRecyclerView(recyclerView) ``` #### 五、优化动体验 在`res/anim/slide_in_right.xml`中添加页面切换动画: ```xml <set xmlns:android="http://schemas.android.com/apk/res/android"> <translate android:fromXDelta="100%" android:toXDelta="0%" android:duration="300"/> </set> ``` ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值