Android实现3D画廊效果

龙旋

共 9709字,需浏览 20分钟

 ·

2021-12-12 20:13

使用ViewPager打造的3D画廊,先看效果图:



需求点:
        1.中间item放大
2.中间item覆盖在两侧item上
3.点击或者滑动两侧的item可以切换ViewPager的当前展示页面

所以首先我们想到的肯定是ViewGroup的clipChildren属性,设为false,可以让子view突破ViewGroup的限制呈现出来突出来的效果,或者是本文这种ViewPager的覆盖效果,网上也看到用RecyclerView实现类似效果的,有兴趣的自行百度.


下面我们看代码怎么实现:


首先在我的项目中这个3D Gallery是一个RecyclerView的Item,并且外部的界面是可左右滑动切花的Fragment,先说明一下下面有些逻辑会处理滑动冲突.
先看下布局,主界面只有一个RecyclerView就不贴代码了.

看下Gallery的布局吧:
android:layout_width="match_parent"android:layout_height="match_parent"android:clipChildren="false"android:id="@+id/rl_vp_container"android:orientation="vertical">    android:layout_toLeftOf="@+id/viewpager"    android:id="@+id/view_left"    android:layout_width="match_parent"    android:layout_height="303dp"    />    android:layout_toRightOf="@+id/viewpager"    android:id="@+id/view_right"    android:layout_width="match_parent"    android:layout_height="303dp"    />    android:layout_centerHorizontal="true"    android:layout_width="200dp"    android:layout_height="303dp"    android:id="@+id/viewpager"/>    android:layout_centerHorizontal="true"    android:layout_below="@+id/viewpager"    android:layout_marginTop="8dp"    android:id="@+id/tv_star_desc"    android:layout_gravity="center_horizontal"    android:layout_width="wrap_content"    android:layout_height="wrap_content"/>


这里可以看出虽然我们的ViewPager看起来是铺满屏幕的,但是其实除了中间的Item两侧是用StarView进行占位的,它的作用是拦截处理点击和触摸两侧可以切换ViewPager的展示Item,但是由于项目中整个界面是可以左右滑动切换的,所以我稍后的代码中拦截了横向滑动,如果不需要请自行忽略.还有个ViewPager的Item的布局也贴一下吧:
                      android:layout_width="match_parent"                  android:layout_height="match_parent"                  android:orientation="vertical"                  android:gravity="center_horizontal"                  android:scaleX="0.85"                  android:scaleY="0.85"                  android:elevation="6dp"    >        
android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" >
android:adjustViewBounds="true" android:id="@+id/iv_star_item" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitXY" />
android:layout_marginRight="6dp" android:layout_marginBottom="8dp" android:layout_alignParentRight="true" android:layout_alignParentBottom="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:background="#00000000" > android:gravity="bottom" android:id="@+id/tv_index" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FFFFFFFF" android:textSize="20sp" android:shadowDy="2" android:shadowDx="0" android:shadowRadius="4" android:text="2" android:shadowColor="#80000000"/> android:gravity="bottom" android:id="@+id/tv_star_total" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#FFFFFFFF" android:textSize="12sp" android:shadowDy="2" android:shadowDx="0" android:shadowRadius="4" android:text="/10" android:shadowColor="#80000000" /> android:visibility="visible" android:id="@+id/iv_cover" android:src="@mipmap/bg_40black" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" />


简单说下,左后一个ImageView是做两侧小的Item半透明效果的,直接覆盖了一层半透明效果的图片在上面.在ViewPager切换效果的管理类中管理缩放和透明度,看下代码:
public class RotationPageTransformer implements ViewPager.PageTransformer{    private static final float MIN_SCALE=0.85f;    private static final float MIN_ALPHA=0.6f;
public void setContext(Context context) { mContext = context; }
private Context mContext; @Override public void transformPage(View page, float position) { float scaleFactor = Math.max(MIN_SCALE,1 - Math.abs(position)); float scaleAlpha = Math.max(MIN_ALPHA,1 - Math.abs(position)); ImageView imageTag= (ImageView) page.findViewById(R.id.iv_cover); float rotate = 0; //position小于等于1的时候,代表page已经位于中心item的最左边, //此时设置为最小的缩放率以及最大的旋转度数 if (position <= -1){ page.setScaleX(MIN_SCALE); page.setScaleY(MIN_SCALE); imageTag.setAlpha((float) 1); }//position从0变化到-1,page逐渐向左滑动 else if (position < 0){ imageTag.setAlpha(Math.abs(position)); page.setScaleX(scaleFactor); page.setScaleY(scaleFactor); }//position从0变化到1,page逐渐向右滑动 else if (position >=0 && position < 1){ imageTag.setAlpha(position); page.setScaleX(scaleFactor); page.setScaleY(scaleFactor); }//position大于等于1的时候,代表page已经位于中心item的最右边 else if (position >= 1){ imageTag.setAlpha((float)1); page.setScaleX(scaleFactor); page.setScaleY(scaleFactor);        } }}

然后看下给ViewPager数据以及拦截左右滑动,贴下代码:
public class MainAdapter extends RecyclerView.Adapter {    private Context mContext;    private float downX ;    //按下时 的X坐标    private float downY ;    //按下时 的Y坐标    private int currentPosition;    private int mOriginSize;    private List dataList=new ArrayList<>();
public MainAdapter(Context context,List list) { this.mContext=context; dataList.addAll(list); }
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(mContext).inflate(R.layout.layout_main_star1, parent, false); return new MainStarViewHolder(view); }
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { // 所以在空白的左右两侧各填充了透明的View(StarView自定义View拦截左右方向滑动事件),用于交互,左侧View向右滑动和点击ViewPager切换 // ,右侧View向左滑动和点击切换ViewPager,其它不做处理 final MainStarViewHolder mainStarHolder = (MainStarViewHolder) holder; mainStarHolder.mViewPager.setPageTransformer(true, new RotationPageTransformer()); mainStarHolder.mViewPager.setOffscreenPageLimit(2); DisplayMetrics dm = mContext.getApplicationContext().getResources().getDisplayMetrics(); //不同分辨率适配 int width = dm.widthPixels; if (width > 800 && width <= 1080) { mainStarHolder.mViewPager.setPageMargin(-200); } else if (width > 1080) { mainStarHolder.mViewPager.setPageMargin(-280); } else { mainStarHolder.mViewPager.setPageMargin(-180); }
mainStarHolder.viewLeft.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { //左侧透明View仅在向右滑时切换ViewPager float x= event.getX(); float y = event.getY(); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: //将按下时的坐标存储 downX = x; downY = y; break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: //获取到距离差 float dx= x-downX; float dy = y-downY; //防止是按下也判断 //通过距离差判断方向 int orientation = getOrientation(dx, dy); switch (orientation) { case 'r': //向右滑动 mainStarHolder.mViewPager.setCurrentItem(mainStarHolder.mViewPager.getCurrentItem()-1,true); return true; case '0': //点击 mainStarHolder.mViewPager.setCurrentItem(mainStarHolder.mViewPager.getCurrentItem()-1,true); return true; case 'l': //向左滑动 return true; } break; } return true; } }); mainStarHolder.viewRight.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { //右侧透明View仅在向右滑时切换ViewPager float x= event.getX(); float y = event.getY(); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: //将按下时的坐标存储 downX = x; downY = y; break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: //获取到距离差 float dx= x-downX; float dy = y-downY; //通过距离差判断方向 int orientation = getOrientation(dx, dy); switch (orientation) { case 'r': //向右滑动 return true; case '0': //点击 mainStarHolder.mViewPager.setCurrentItem(mainStarHolder.mViewPager.getCurrentItem()+1,true); return true; case 'l': //向左滑动 if(event.getAction()== MotionEvent.ACTION_UP || event.getAction()== MotionEvent.ACTION_CANCEL ){ mainStarHolder.mViewPager.setCurrentItem(mainStarHolder.mViewPager.getCurrentItem()+1,true); } return true; } break; } return true; } }); mOriginSize = dataList.size(); initStarAdapter(mainStarHolder, dataList); mainStarHolder.mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override public void onPageSelected(int position) { currentPosition = position; mainStarHolder.mViewPager.setTranslationX(1); }
@Override public void onPageScrollStateChanged(int state) { } }); }
@Override public int getItemCount() { return 1; }
private void initStarAdapter(MainStarViewHolder mainStarHolder, List dataList) { final MainStarAdapter mainStarAdapter = new MainStarAdapter(dataList, mContext, mainStarHolder.mViewPager, mOriginSize); mainStarHolder.mViewPager.setAdapter(mainStarAdapter); mainStarHolder.mViewPager.setCurrentItem( 20*mOriginSize + currentPosition); } //获取滑动方向 private int getOrientation(float dx, float dy) { if (Math.abs(dx)>Math.abs(dy)){ //X轴移动 if(dx==0){//点击 return '0'; } return dx>0?'r':'l'; }else{ //Y轴移动 if(dy==0){//点击 return '0'; } return dy>0?'b':'t'; } }}

最核心的逻辑:如果用Android原生的ViewPager来实现你会发现左边的Item会覆盖在中间的Item之上.所以我们要自定义一下ViewPager,重写一下他的排序方法:
public class CustomViewPager extends ViewPager {    private ArrayList childCenterXAbs = new ArrayList<>();    private SparseArray childIndex = new SparseArray<>();
public CustomViewPager(Context context) { super(context); init(); }
public CustomViewPager(Context context, AttributeSet attrs) { super(context, attrs); init(); }
private void init(){ setClipToPadding(false); setOverScrollMode(OVER_SCROLL_NEVER); }

/** * @param childCount * @param n * @return 第n个位置的child 的绘制索引 */ @Override protected int getChildDrawingOrder(int childCount, int n) { if (n == 0 || childIndex.size() != childCount) { childCenterXAbs.clear(); childIndex.clear(); int viewCenterX = getViewCenterX(this); for (int i = 0; i < childCount; ++i) { int indexAbs = Math.abs(viewCenterX - getViewCenterX(getChildAt(i))); //两个距离相同,后来的那个做自增,从而保持abs不同 if (childIndex.get(indexAbs) != null) { ++indexAbs; } childCenterXAbs.add(indexAbs); childIndex.append(indexAbs, i); } Collections.sort(childCenterXAbs);//1,0,2 0,1,2 } //那个item距离中心点远一些,就先draw它。(最近的就是中间放大的item,最后draw) return childIndex.get(childCenterXAbs.get(childCount - 1 - n)); }
private int getViewCenterX(View view) { int[] array = new int[2]; view.getLocationOnScreen(array); return array[0] + view.getWidth() / 2; }}


源码地址:
https://github.com/FrizzleLiu/3DGalleryDemo


到这里就结束啦。
浏览 51
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报