欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

实现3D层叠式卡片图片展示

程序员文章站 2022-07-13 09:12:11
...

先看效果

实现3D层叠式卡片图片展示

好了效果看了,感兴趣的往下看哦!

整体实现思路

  1. 重写RelativeLayout 实现 锁定宽高比例的 RelativeLayout
  2. 自定义一个支持滑动的面板 继承 ViewGroup
  3. 卡片View绘制
  4. 页面中使用布局

首先为了更好的展示图片我们重写一下 RelativeLayout 编写一个锁定宽高比例的 RelativeLayout

AutoScaleRelativeLayout

public class AutoScaleRelativeLayout extends RelativeLayout {
    //宽高比例
    private float widthHeightRate = 0.35f;

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

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

    public AutoScaleRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //通过布局获取宽高比例
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.card, 0, 0);
        widthHeightRate = a.getFloat(R.styleable.card_widthHeightRate, widthHeightRate);
        a.recycle();
    }

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

        // 调整高度
        int width = getMeasuredWidth();
        int height = (int) (width * widthHeightRate);
        ViewGroup.LayoutParams lp = getLayoutParams();
        lp.height = height;
        setLayoutParams(lp);
    }
}

这样我们就编写好了我们想要的父布局

使用方法

   <com.petterp.toos.ImageCard.AutoScaleRelativeLayout
        android:id="@+id/card_top_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        card:widthHeightRate="0.6588">
<!--        widthHeightRate:就是设置宽高的百分比-->
        <ImageView
            android:id="@+id/card_image_view"
            android:layout_width="fill_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY" />
<!--                这是我们展示的图片-->
        <View
            android:id="@+id/maskView"
            android:layout_width="fill_parent"
            android:layout_height="match_parent"
            android:background="?android:attr/selectableItemBackground"
            android:clickable="true" />
<!--        这个是为了让我们图片上有波纹-->
    </com.petterp.toos.ImageCard.AutoScaleRelativeLayout>

接下来就是主要布局,也就是展示图片的布局了

为了实现滑动我们编写一个支持滑动的画板

//事件处理
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int action = ev.getActionMasked();
        // 按下时保存坐标信息
        if (action == MotionEvent.ACTION_DOWN) {
            this.downPoint.x = (int) ev.getX();
            this.downPoint.y = (int) ev.getY();
        }
        return super.dispatchTouchEvent(ev);
    }

    /* touch事件的拦截与处理都交给mDraghelper来处理 */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean shouldIntercept = mDragHelper.shouldInterceptTouchEvent(ev);
        boolean moveFlag = moveDetector.onTouchEvent(ev);
        int action = ev.getActionMasked();
        if (action == MotionEvent.ACTION_DOWN) {
            // ACTION_DOWN的时候就对view重新排序
            if (mDragHelper.getViewDragState() == ViewDragHelper.STATE_SETTLING) {
                mDragHelper.abort();
            }
            orderViewStack();

            // 保存初次按下时arrowFlagView的Y坐标
            // action_down时就让mDragHelper开始工作,否则有时候导致异常
            mDragHelper.processTouchEvent(ev);
        }

        return shouldIntercept && moveFlag;
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        try {
            // 统一交给mDragHelper处理,由DragHelperCallback实现拖动效果
            // 该行代码可能会抛异常,正式发布时请将这行代码加上try catch
            mDragHelper.processTouchEvent(e);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return true;
    }
    //计算
    @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));

        allWidth = getMeasuredWidth();
        allHeight = getMeasuredHeight();
    }
    //定位
    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
                            int bottom) {
        // 布局卡片view
        int size = viewList.size();
        for (int i = 0; i < size; i++) {
            View viewItem = viewList.get(i);
            int childHeight = viewItem.getMeasuredHeight();
            int viewLeft = (getWidth() - viewItem.getMeasuredWidth()) / 2;
            viewItem.layout(viewLeft, itemMarginTop, viewLeft + viewItem.getMeasuredWidth(), itemMarginTop + childHeight);
            int offset = yOffsetStep * i;
            float scale = 1 - SCALE_STEP * i;
            if (i > 2) {
                // 备用的view
                offset = yOffsetStep * 2;
                scale = 1 - SCALE_STEP * 2;
            }

            viewItem.offsetTopAndBottom(offset);
            viewItem.setScaleX(scale);
            viewItem.setScaleY(scale);
        }

        // 布局底部按钮的View
        if (null != bottomLayout) {
            int layoutTop = viewList.get(0).getBottom() + bottomMarginTop;
            bottomLayout.layout(left, layoutTop, right, layoutTop
                    + bottomLayout.getMeasuredHeight());
        }

        // 初始化一些中间参数
        initCenterViewX = viewList.get(0).getLeft();
        initCenterViewY = viewList.get(0).getTop();
        childWith = viewList.get(0).getMeasuredWidth();
    }
      //onFinishInflate 当View中所有的子控件均被映射成xml后触发
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        // 渲染完成,初始化卡片view列表
        viewList.clear();
        int num = getChildCount();
        for (int i = num - 1; i >= 0; i--) {
            View childView = getChildAt(i);
            if (childView.getId() == R.id.card_bottom_layout) {
                bottomLayout = childView;
                initBottomLayout();
            } else {
                // for循环取view的时候,是从外层往里取
                CardItemView viewItem = (CardItemView) childView;
                viewItem.setParentView(this);
                viewItem.setTag(i + 1);
                viewItem.maskView.setOnClickListener(btnListener);
                viewList.add(viewItem);
            }
        }

        CardItemView bottomCardView = viewList.get(viewList.size() - 1);
        bottomCardView.setAlpha(0);
    }

卡片View绘制

 private void initSpring() {
        SpringConfig springConfig = SpringConfig.fromBouncinessAndSpeed(15, 20);
        SpringSystem mSpringSystem = SpringSystem.create();
        springX = mSpringSystem.createSpring().setSpringConfig(springConfig);
        springY = mSpringSystem.createSpring().setSpringConfig(springConfig);

        springX.addListener(new SimpleSpringListener() {
            @Override
            public void onSpringUpdate(Spring spring) {
                int xPos = (int) spring.getCurrentValue();
                setScreenX(xPos);
                parentView.onViewPosChanged(CardItemView.this);
            }
        });

        springY.addListener(new SimpleSpringListener() {
            @Override
            public void onSpringUpdate(Spring spring) {
                int yPos = (int) spring.getCurrentValue();
                setScreenY(yPos);
                parentView.onViewPosChanged(CardItemView.this);
            }
        });
    }
    //装载数据
    public void fillData(CardDataItem itemData) {
        Glide.with(getContext()).load(itemData.imagePath).into(imageView);


    }
    /**
     * 动画移动到某个位置
     */
    public void animTo(int xPos, int yPos) {
        setCurrentSpringPos(getLeft(), getTop());
        springX.setEndValue(xPos);
        springY.setEndValue(yPos);
    }

    /**
     * 设置当前spring位置
     */
    private void setCurrentSpringPos(int xPos, int yPos) {
        springX.setCurrentValue(xPos);
        springY.setCurrentValue(yPos);
    }

接下来我们需要使用它 编写Fragment布局

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



    <com.petterp.toos.ImageCard.CardSlidePanel
        android:id="@+id/image_slide_panel"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        card:bottomMarginTop="38dp"
        card:itemMarginTop="10dp"
        card:yOffsetStep="26dp">

        <LinearLayout
            android:id="@+id/card_bottom_layout"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:orientation="horizontal">

            <Button
                android:background="#03A9F4"
                android:text="右侧移除"
                android:id="@+id/card_left_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
          />


            <Button
                android:background="#03A9F4"
                android:text="右侧移除"
                android:id="@+id/card_right_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
             />
        </LinearLayout>

        <com.petterp.toos.ImageCard.CardItemView
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <com.petterp.toos.ImageCard.CardItemView
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <com.petterp.toos.ImageCard.CardItemView
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <com.petterp.toos.ImageCard.CardItemView
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </com.petterp.toos.ImageCard.CardSlidePanel>

</LinearLayout>

代码中的使用

 private void initView(View rootView) {
        CardSlidePanel slidePanel = (CardSlidePanel) rootView
                .findViewById(R.id.image_slide_panel);
        cardSwitchListener = new CardSlidePanel.CardSwitchListener() {

            @Override
            public void onShow(int index) {
                Toast.makeText(getContext(), "CardFragment"+"正在显示=" +index, Toast.LENGTH_SHORT).show();

            }
            //type 0=右边 ,-1=左边
            @Override
            public void onCardVanish(int index, int type) {
                Toast.makeText(getContext(), "CardFragment"+ "正在消失=" + index + " 消失type=" + type, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onItemClick(View cardView, int index) {
                Toast.makeText(getContext(), "CardFragment"+"卡片点击=" + index, Toast.LENGTH_SHORT).show();
            }
        };
        slidePanel.setCardSwitchListener(cardSwitchListener);
        prepareDataList();
        slidePanel.fillData(dataList);
    }
    //封装数据
    private void prepareDataList() {
        int num = imagePaths.length;
        //重复添加数据10次(测试数据太少)
        for (int j = 0; j < 10; j++) {
            for (int i = 0; i < num; i++) {
                CardDataItem dataItem = new CardDataItem();
                dataItem.imagePath = imagePaths[i];
                dataList.add(dataItem);
            }
        }
    }

到此主要逻辑代码就编写完了==

详细说明代码中已经注释 ,全部代码请看源码

源码:github源码

源码中的TestCardFragment 为使用模板