用BVP一比一还原自如客APP裸眼3D效果(Android原生)

前几天,自如大前端开源了一个裸眼3D效果的Banner轮播图的实现方案。看着非常有意思,于是趁着空闲时间结合我的开源库BannerViewPager码了一个自如裸眼3D效果的demo。demo基本实现了自如APP的Banner效果。

关于实现原理,自如客APP裸眼3D效果的实现这篇文章已经写得很清楚了,本篇文章就不再赘述了,这里主要看一下代码实现。

一、监听传感器的ViewSensorLayout

裸眼3D效果的核心其实就是SensorLayout的实现,这个View通过监听传感器来计算View的位移,然后通过Scroller进行滑动。首先,在构造方法中设置传感器的监听事件:

public SensorLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    mScroller = new Scroller(context);
    mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
    // 重力传感器
    if (mSensorManager != null) {
        Sensor accelerateSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        // 地磁场传感器
        Sensor magneticSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        mSensorManager.registerListener(this, accelerateSensor, SensorManager.SENSOR_DELAY_GAME);
        mSensorManager.registerListener(this, magneticSensor, SensorManager.SENSOR_DELAY_GAME);
    }
}

然后,在传感器发生变化的时候通过Scroller来移动View,代码如下:

@Override
public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
        mAccelerateValues = event.values;
    }
    if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
        mMagneticValues = event.values;
    }
    float[] values = new float[3];
    float[] R = new float[9];
    if (mMagneticValues != null && mAccelerateValues != null)
        SensorManager.getRotationMatrix(R, null, mAccelerateValues, mMagneticValues);
    SensorManager.getOrientation(R, values);
    // x轴的偏转角度
    values[1] = (float) Math.toDegrees(values[1]);
    // y轴的偏转角度
    values[2] = (float) Math.toDegrees(values[2]);
    double degreeX = values[1];
    double degreeY = values[2];
    if (degreeY <= 0 && degreeY > mDegreeYMin) {
        hasChangeX = true;
        scrollX = (int) (degreeY / Math.abs(mDegreeYMin) * mXMoveDistance * mDirection);
    } else if (degreeY > 0 && degreeY < mDegreeYMax) {
        hasChangeX = true;
        scrollX = (int) (degreeY / Math.abs(mDegreeYMax) * mXMoveDistance * mDirection);
    }
    if (degreeX <= 0 && degreeX > mDegreeXMin) {
        hasChangeY = true;
        scrollY = (int) (degreeX / Math.abs(mDegreeXMin) * mYMoveDistance * mDirection);
    } else if (degreeX > 0 && degreeX < mDegreeXMax) {
        hasChangeY = true;
        scrollY = (int) (degreeX / Math.abs(mDegreeXMax) * mYMoveDistance * mDirection);
    }
    smoothScroll(hasChangeX ? scrollX : mScroller.getFinalX(), hasChangeY ? scrollY : mScroller.getFinalY());
}

代码中的mDirection表示的是移动的方向,这个参数会开放给外面,来设置跟随传感器移动还是与传感器反向移动。
而smoothScroll通过Scroller实现弹性滑动,代码如下:

public void smoothScroll(int destX, int destY) {
    int scrollY = getScrollY();
    int delta = destY - scrollY;
    mScroller.startScroll(destX, scrollY, 0, delta, 200);
    invalidate();
}

@Override
public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
        postInvalidate();
    }
}

上述的代码核心来自自如客APP裸眼3D效果的实现。通过一下布局,便可以实现裸眼3D的效果了。

<FrameLayout android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android">

  <com.zhpan.sample.banner3d.SensorLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent">

    <ImageView
        android:id="@+id/iv_background"
        android:layout_width="match_parent"
        android:scaleType="centerCrop"
        android:scaleX="1.3"
        android:layout_height="match_parent" />

  </com.zhpan.sample.banner3d.SensorLayout>

  <ImageView
      android:id="@+id/iv_mid"
      android:layout_width="match_parent"
      android:scaleType="fitXY"
      android:layout_marginEnd="@dimen/dp_16"
      android:layout_marginStart="@dimen/dp_16"
      android:layout_gravity="bottom"
      android:layout_height="@dimen/dp_100" />

  <com.zhpan.sample.banner3d.SensorLayout
      android:id="@+id/sensor_layout"
      android:layout_gravity="bottom"
      android:layout_width="match_parent"
      android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/iv_foreground"
        android:layout_width="match_parent"
        android:scaleType="fitXY"
        android:layout_marginStart="@dimen/dp_16"
        android:layout_marginEnd="@dimen/dp_16"
        android:layout_height="@dimen/dp_100" />

  </com.zhpan.sample.banner3d.SensorLayout>

</FrameLayout>

123.gif

二、BannerViewPager实现裸眼3D轮播图

自如APP的Banner是两个Banner叠加在一起来实现的。这里我稍微做了一些优化,即背景层使用ImageView也可以达到一样的效果,而前景层使用BannerViewPager实现无线轮播。布局文件代码如下:

<FrameLayout 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.zhpan.sample.banner3d.SensorLayout
      android:id="@+id/sensor_layout"
      android:layout_width="match_parent"
      android:layout_height="@dimen/dp_200">

    <ImageView
        android:id="@+id/iv_background"
        android:layout_width="match_parent"
        android:scaleType="centerCrop"
        android:scaleX="1.3"
        android:layout_height="match_parent" />

  </com.zhpan.sample.banner3d.SensorLayout>

  <com.zhpan.bannerview.BannerViewPager
      android:id="@+id/bvp_foreground"
      android:layout_width="match_parent"
      android:layout_height="@dimen/dp_220" />

</FrameLayout>

Activity中实现轮播图的代码如下:

mBannerForeground.apply {
  // 为BannerViewPager设置Adapter
  adapter = ForegroundAdapter()
  // 设置自动轮播
  setAutoPlay(true)
  // 设置指示器相关样式
  setIndicatorStyle(IndicatorStyle.ROUND_RECT)
  setIndicatorSlideMode(IndicatorSlideMode.SCALE)
  setIndicatorSliderWidth(
    resources.getDimensionPixelOffset(R.dimen.dp_7),
    resources.getDimensionPixelOffset(R.dimen.dp_10)
  )
  setIndicatorMargin(0, 0, resources.getDimensionPixelOffset(R.dimen.dp_16), 0)
  setIndicatorSliderColor(
    resources.getColor(R.color.gray_88),
    resources.getColor(R.color.dark_gray)
  )
  setIndicatorGravity(IndicatorGravity.END)
  setScrollDuration(800)
  setIndicatorSliderGap(resources.getDimensionPixelOffset(R.dimen.dp_3))
} // 页面切换的监听
.registerOnPageChangeCallback(object : OnPageChangeCallback() {
  override fun onPageSelected(position: Int) {
    val banner3DData = mBannerForeground.data[position]
    // 切换页面后改变背景
    mIvBackGround.setImageResource(banner3DData?.background!!)
  }
}).create(arrayList)

BannerViewPager的使用非常简单,可以轻松实现一个带有指示器的,且能无限轮播的Banner。如果你不了解BannerViewPager,可以到GitHub主页查看使用步骤。

最终,实现的效果几乎与自如APP一模一样。如下图:

ezgif-3-aa7e1b542b89.gif

注:图片来源自如APP

最后,相关代码我已提交至GitHub:AndroidSample

同时,欢迎关注BannerViewPager轮播图库。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值