Android中间底部可滚动内容

问题描述 投票:0回答:1

我在Android中面临BottomSheet的1个问题。我想要实现的目标:

BottomSheet Google Direction

但我无法像谷歌一样使用页眉和页脚实现ListView。

主要问题是ListView显示所有项目和我的底部工作表不显示页脚视图。我希望始终可以看到底部工作表的底部视图。

android google-maps bottom-sheet
1个回答
0
投票

我已经创建了一个示例示例来实现底层作为google方向,将漂浮在任何ui上,并且工作将与google direction swipable view相同。我使用“ViewDragHelper”类(https://developer.android.com/reference/android/support/v4/widget/ViewDragHelper)实现了它

以下是我使用“ViewDragHelper”实现的与google direction swipable视图相同的示例:

enter image description here

注意:在下面的示例中,存在硬编码字符串以及仅在可转换视图类中采用的单个适配器,并且还采用静态列表。任何人都可以使用正确的代码格式以及getter / setter来自定义它。这仅仅是为了教导“ViewDragHelper”如何工作。

首先创建“GoogleBottomSheet”类,如下所示:

import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;

public class GoogleBottomSheet extends ViewGroup {

    private final ViewDragHelper mDragHelper;
    GoogleRoutesAdapter googleRoutesAdapter;

    private View mHeaderView;
    private RecyclerView rvList;

    private float mInitialMotionX;
    private float mInitialMotionY;

    private int mDragRange;
    private int mTop;
    private float mDragOffset;


    public GoogleBottomSheet(Context context) {
        this(context, null);
    }

    public GoogleBottomSheet(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mHeaderView = findViewById(R.id.viewHeader);
        rvList = findViewById(R.id.rvList);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);
        rvList.setLayoutManager(linearLayoutManager);

        ArrayList<String> allRoutesList = new ArrayList<>();
        allRoutesList.add("47 Bourbon Li");
        allRoutesList.add("Head South");
        allRoutesList.add("Princess Street");
        allRoutesList.add("A 3-lane partially one-way street heading out of Manchester city centre");
        allRoutesList.add("Manchester Jewish Museum, \n" +
                "Peninsula Building");
        allRoutesList.add("Portland Street");
        allRoutesList.add("Quay Street");
        allRoutesList.add("Forms part of the city's historic Northern Quarter district");
        allRoutesList.add("Sackville Street Building, University of Manchester including the Godlee Observatory");
        allRoutesList.add("Turn Left on S Naper");
        allRoutesList.add("150 W-Stall");
        allRoutesList.add("Former National Westminster Bank");
        allRoutesList.add("Bradshaw, L. D.");
        allRoutesList.add("House of Commons Transport Committee");
        allRoutesList.add("A street only for Metrolink trams and previously buses which joined the street at Lower Mosley Street.");
        googleRoutesAdapter = new GoogleRoutesAdapter(getContext(), allRoutesList);
        rvList.setAdapter(googleRoutesAdapter);
    }

    public GoogleBottomSheet(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mDragHelper = ViewDragHelper.create(this, 1f, new DragHelperCallback());
    }

    public void maximize() {
        smoothSlideTo(0f);
    }

    boolean smoothSlideTo(float slideOffset) {
        final int topBound = getPaddingTop();
        int y = (int) (topBound + slideOffset * mDragRange);

        if (mDragHelper.smoothSlideViewTo(mHeaderView, mHeaderView.getLeft(), y)) {
            ViewCompat.postInvalidateOnAnimation(this);
            return true;
        }
        return false;
    }

    private class DragHelperCallback extends ViewDragHelper.Callback {

        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child == mHeaderView;
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            mTop = top;

            mDragOffset = (float) top / mDragRange;

            requestLayout();
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            int top = getPaddingTop();
            if (yvel > 0 || (yvel == 0 && mDragOffset > 0.5f)) {
                top += mDragRange;
            }
            mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);
        }

        @Override
        public int getViewVerticalDragRange(View child) {
            return mDragRange;
        }

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            final int topBound = getPaddingTop();
            final int bottomBound = getHeight() - mHeaderView.getHeight();

            final int newTop = Math.min(Math.max(top, topBound), bottomBound);
            return newTop;
        }

    }

    @Override
    public void computeScroll() {
        if (mDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = MotionEventCompat.getActionMasked(ev);

        if ((action != MotionEvent.ACTION_DOWN)) {
            mDragHelper.cancel();
            return super.onInterceptTouchEvent(ev);
        }

        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
            mDragHelper.cancel();
            return false;
        }

        final float x = ev.getX();
        final float y = ev.getY();
        boolean interceptTap = false;

        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                mInitialMotionX = x;
                mInitialMotionY = y;
                interceptTap = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);
                break;
            }

            case MotionEvent.ACTION_MOVE: {
                final float adx = Math.abs(x - mInitialMotionX);
                final float ady = Math.abs(y - mInitialMotionY);
                final int slop = mDragHelper.getTouchSlop();
                if (ady > slop && adx > ady) {
                    mDragHelper.cancel();
                    return false;
                }
            }
        }

        return mDragHelper.shouldInterceptTouchEvent(ev) || interceptTap;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        mDragHelper.processTouchEvent(ev);

        final int action = ev.getAction();
        final float x = ev.getX();
        final float y = ev.getY();

        boolean isHeaderViewUnder = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);
        switch (action & MotionEventCompat.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN: {
                mInitialMotionX = x;
                mInitialMotionY = y;
                break;
            }

            case MotionEvent.ACTION_UP: {
                final float dx = x - mInitialMotionX;
                final float dy = y - mInitialMotionY;
                final int slop = mDragHelper.getTouchSlop();
                if (dx * dx + dy * dy < slop * slop && isHeaderViewUnder) {
                    if (mDragOffset == 0) {
                        smoothSlideTo(1f);
                    } else {
                        smoothSlideTo(0f);
                    }
                }
                break;
            }
        }


        return isHeaderViewUnder && isViewHit(mHeaderView, (int) x, (int) y) ||
                isViewHit(rvList, (int) x, (int) y);
    }


    private boolean isViewHit(View view, int x, int y) {
        int[] viewLocation = new int[2];
        view.getLocationOnScreen(viewLocation);
        int[] parentLocation = new int[2];
        this.getLocationOnScreen(parentLocation);
        int screenX = parentLocation[0] + x;
        int screenY = parentLocation[1] + y;
        return screenX >= viewLocation[0] && screenX < viewLocation[0] + view.getWidth() &&
                screenY >= viewLocation[1] && screenY < viewLocation[1] + view.getHeight();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        int maxWidth = MeasureSpec.getSize(widthMeasureSpec);
        int maxHeight = MeasureSpec.getSize(heightMeasureSpec);

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
                resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mDragRange = getHeight() - mHeaderView.getHeight();

        mHeaderView.layout(
                0,
                mTop,
                r,
                mTop + mHeaderView.getMeasuredHeight());

        rvList.layout(
                0,
                mTop + mHeaderView.getMeasuredHeight(),
                r,
                mTop + b);
    }
}

为recyclerview视图项创建名为“rawitem_mapdetails.xml”的xml文件,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/mTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/colorPrimaryDark"
            android:text="Route 1"
            android:padding="@dimen/_10sdp"
            android:textColor="@android:color/white"
            android:textSize="@dimen/_15sdp" />

    </RelativeLayout>

</LinearLayout>

为recyclerview创建名为“GoogleRoutesAdapter”的简单适配器,如下所示:

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.TextView;
import android.widget.Toast;

import java.util.ArrayList;

public class GoogleRoutesAdapter extends RecyclerView.Adapter<GoogleRoutesAdapter.GoogleRouteViewHolder> {

    private Context mContext;
    private ArrayList<String> allRoutesList;

    public GoogleRoutesAdapter(Context context, ArrayList<String> allRoutesList) {
        this.mContext = context;
        this.allRoutesList = allRoutesList;
    }

    @Override
    public GoogleRouteViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View layoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.rawitem_mapdetails, null);
        GoogleRouteViewHolder rcv = new GoogleRouteViewHolder(layoutView);
        return rcv;
    }

    @Override
    public void onBindViewHolder(final GoogleRouteViewHolder holder, final int position) {
        holder.tvRoute.setText(allRoutesList.get(position));
    }

    @Override
    public int getItemCount() {
        return allRoutesList.size();
    }


    public class GoogleRouteViewHolder extends RecyclerView.ViewHolder {
        private TextView tvRoute;

        public GoogleRouteViewHolder(View view) {
            super(view);
            tvRoute = view.findViewById(R.id.mTextView);
            tvRoute.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(mContext, allRoutesList.get(getAdapterPosition()), Toast.LENGTH_SHORT).show();
                }
            });
        }

    }

}

为MainActivity创建如下的“activity_main.xml”,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.viewdraghelper.GoogleBottomSheet
        android:id="@+id/my_googleBottomSheet"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/viewHeader"
            android:layout_width="match_parent"
            android:background="@color/colorAccent"
            android:layout_height="@dimen/_80sdp"
            android:textSize="@dimen/_25sdp"
            android:padding="@dimen/_10sdp"
            android:textColor="@android:color/white"
            android:text="31 min (29 mi)"/>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/rvList"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </com.viewdraghelper.GoogleBottomSheet>

</RelativeLayout>

根据以下要求编辑答案:

1.在第一次创建的视图上将滑动面板置于底部/隐藏为默认状态

首先采用initOnce全局布尔变量

private boolean initOnce = false;

然后更改onLayout()方法如下:

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if(!initOnce){
            initOnce = true;
            mDragRange = getHeight() - mHeaderView.getHeight();
            mHeaderView.layout(
                    0,
                    b - mHeaderView.getMeasuredHeight(),
                    r,
                    b);
        }else {
            mDragRange = getHeight() - mHeaderView.getHeight();

            mHeaderView.layout(
                    0,
                    mTop,
                    r,
                    mTop + mHeaderView.getMeasuredHeight());

            rvList.layout(
                    0,
                    mTop + mHeaderView.getMeasuredHeight(),
                    r,
                    mTop + b);
        }
    }

现在都完成了!正如我上面所说,这只是教导“ViewDragHelper”如何工作,这就是为什么我们现在不必在MainActivity中做任何事情,因为所有适配器逻辑都驻留在“GoogleBottomSheet”类中。我还点了一个简单的recyclerview项目,这样任何人都可以更好地了解其他ui将与其自己的行为相同。我们还可以通过在“GoogleBottomSheet”中添加任何ui来进行自定义。

希望能帮助到你!快乐编码:)

© www.soinside.com 2019 - 2024. All rights reserved.