使用ViewPager打造的3D畫廊,先看效果圖:
需求點:
1.中間item放大
2.中間item覆蓋在兩側item上
3.點擊或者滑動兩側的item可以切換ViewPager的當前展示頁面
所以首先我們想到的肯定是ViewGroup的clipChildren屬性,設爲false,可以讓子view突破ViewGroup的限制呈現出來突出來的效果,或者是本文這種ViewPager的覆蓋效果,網上也看到用RecyclerView實現類似效果的,有興趣的自行百度.
下面我們看代碼怎麼實現:
首先在我的項目中這個3D Gallery是一個RecyclerView的Item,並且外部的界面是可左右滑動切花的Fragment,先說明一下下面有些邏輯會處理滑動衝突.
先看下佈局,主界面只有一個RecyclerView就不貼代碼了.
看下Gallery的佈局吧:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:id="@+id/rl_vp_container"
android:orientation="vertical">
<com.bubu.a3dgallerydemo.StarView
android:layout_toLeftOf="@+id/viewpager"
android:id="@+id/view_left"
android:layout_width="match_parent"
android:layout_height="303dp"
/>
<com.bubu.a3dgallerydemo.StarView
android:layout_toRightOf="@+id/viewpager"
android:id="@+id/view_right"
android:layout_width="match_parent"
android:layout_height="303dp"
/>
<com.bubu.a3dgallerydemo.CustomViewPager
android:layout_centerHorizontal="true"
android:layout_width="200dp"
android:layout_height="303dp"
android:id="@+id/viewpager"
/>
<TextView
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"
/>
</RelativeLayout>
這裏可以看出雖然我們的ViewPager看起來是鋪滿屏幕的,但是其實除了中間的Item兩側是用StarView進行佔位的,它的作用是攔截處理點擊和觸摸兩側可以切換ViewPager的展示Item,但是由於項目中整個界面是可以左右滑動切換的,所以我稍後的代碼中攔截了橫向滑動,如果不需要請自行忽略.還有個ViewPager的Item的佈局也貼一下吧:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
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"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:adjustViewBounds="true"
android:id="@+id/iv_star_item"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
/>
<LinearLayout
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"
>
<TextView
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"/>
<TextView
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" />
</LinearLayout>
<ImageView
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"
/>
</RelativeLayout>
</LinearLayout>
簡單說下,左後一個ImageView是做兩側小的Item半透明效果的,直接覆蓋了一層半透明效果的圖片在上面.在ViewPager切換效果的管理類中管理縮放和透明度,看下代碼:
package com.bubu.a3dgallerydemo;
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.widget.ImageView;
/**
* Fixme
* Author: LWJ
* desc:
* Date: 2017/09/19 10:13
* Copyright (c) 2016 d2cmall. All rights reserved.
*/
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數據以及攔截左右滑動,貼下代碼:
package com.bubu.a3dgallerydemo;
import android.content.Context;
import android.content.Intent;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.RecyclerView;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Administrator on 2018/5/10.
* Description : MainAdapter
*/
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<String> dataList=new ArrayList<>();
public MainAdapter(Context context,List<String> 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<String> 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,重寫一下他的排序方法:
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.View;
import java.util.ArrayList;
import java.util.Collections;
/**
* Created by LWJ.
*/
public class CustomViewPager extends ViewPager {
private ArrayList<Integer> childCenterXAbs = new ArrayList<>();
private SparseArray<Integer> 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;
}
}