之前项目开发的时候要实现一种画廊效果,分别利用Gallery或ViewPager实现了该效果:
图中圆角图片是自定义View,Gallery和ViewPager实现效果基本一致。
两种方式对比:Gallery原生支持快速滑动,实现起来稍复杂,ViewPager相比Gallery更流畅,可以设置任意页为第一条,实现相对简单。
Gallery实现画廊
自定义Gallery继承安卓Gallery控件,代码比较简单,只是坑比较多。
首先在构造函数里设置转换可用
public GalleryView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.setStaticTransformationsEnabled(true);//设置为true时,每次viewGroup(看Gallery的源码就可以看到它是从ViewGroup
//接继承过来的)在drawChild的时候都会触发getChildStaticTransformation这个方法
// this.setSpacing(20);
}
获取Gallery和child的中心点,用来计算缩放比例
/**
* 获取Gallery的中心x
*/
private int getCenterOfCoverflow() {
return (getWidth() - getPaddingLeft() - getPaddingRight()) / 2 + getPaddingLeft();
}
/**
* 获取View的中心x
*/
private int getCenterOfView(View view) {
return view.getLeft() + view.getWidth() / 2;
}
当size改变的时候需要重新获取中心点
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mCoveflowCenter = getCenterOfCoverflow(); // size改变后重新获取中心
super.onSizeChanged(w, h, oldw, oldh);
}
设置转换可用后在drawChild的时候会调用getChildStaticTransformation方法,我们可以在getChildStaticTransformation方法里做滑动和缩放
/**
* 安卓15以上默认开启了硬件加速,需特殊处理
*/
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (android.os.Build.VERSION.SDK_INT > 15) {
transformView(child);
}
return super.drawChild(canvas, child, drawingTime);
}
@Override protected boolean getChildStaticTransformation(View child, Transformation trans) {
if (android.os.Build.VERSION.SDK_INT < 16)
transformView(child);
return true; }
@SuppressLint("NewApi")
private void transformView(View child) {
int childCenter = getCenterOfView(child);
float scale = (((float) (mCoveflowCenter - childCenter)) / (float) child.getWidth()) * mMaxScale;
float sxy = 1f - Math.abs(scale);
child.setPivotY(child.getHeight() / 2);
if (childCenter > mCoveflowCenter) {
child.setPivotX(0);
} else {
child.setPivotX(child.getWidth());
}
child.setScaleX(sxy);
child.setScaleY(sxy);
}
这里有个坑就是在系统版本15以上默认开启硬件加速,导致getChildStaticTransformation方法失效,所以需要特殊处理。transformView方法里面对child做缩放处理,获取中心点,计算缩放比例,设置锚点。
适配器关键代码,条目设置LayoutParams
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.widget_gallery_view, null ,false);
convertView.setLayoutParams(new GalleryView.LayoutParams(getWidth(parent.getContext()),getHeight(parent.getContext())));
}
ImageView cardView = (ImageView) convertView.findViewById(R.id.headRIV);
cardView.setImageResource(position % 2 == 0 ? R.mipmap.a1 : R.mipmap.head);
return convertView;
}
宽高是一个写死的比例,使用的时候可以做下改动
public int getWidth(Context context) {
if(displayMetrics == null)
displayMetrics = context.getResources().getDisplayMetrics();
return displayMetrics.widthPixels * 520 / 640;
}
public int getHeight(Context context) {
// if(displayMetrics == null)
// displayMetrics = context.getResources().getDisplayMetrics();
return getWidth(context) * 800 / 520;
}
这里必须要设置LayoutParams才能显示子View,不是很清楚为什么,有知道的不妨评论告知下。
ViewPager实现画廊效果
ViewPager实现此效果关键属性clipChildren,关于这个属性的介绍可以看
这篇文章。
如果你要实现的效果不需要缩放,那么你只需要简单的设置一下属性就可以实现。
1.布局文件中ViewPager和父布局的clipChildren属性设置为false,默认为true
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:clipChildren="false"
android:layout_height="match_parent"
android:weightSum="200"
android:gravity="center_vertical">
<com.itheima.galleryview.view.ViewPagerTransform
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginLeft="40dp"
android:layout_marginRight="40dp"
android:clipChildren="false"
android:layout_weight="120">
</com.itheima.galleryview.view.ViewPagerTransform>
</LinearLayout>
2.你需要给ViewPager的每个页面设置一个间距
// 设置页面间距
viewPager.setPageMargin(20);
只需要这两步你就可以实现画廊效果了,那么我要有缩放效果怎么实现呢?
实现切换效果ViewPager有一个方法setPageTransformer,关于PageTransformer参数的介绍可以看这里,在此效果我们准备了一个PageTransformer,可以参考谷歌官方的两个例子
public class DepthPageTransformer implements ViewPager.PageTransformer {
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@SuppressLint("NewApi")
public void transformPage(View view, float position) {
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
view.setScaleY(MIN_SCALE);
view.setAlpha(MIN_ALPHA);
} else if (position <= 0) { // [-1,0]
// Use the default slide transition when moving to the left page
float scaleFactor = MIN_SCALE + (1 - MIN_SCALE)* (1 - Math.abs(position));
float alphaFactor = MIN_ALPHA + (1 - MIN_ALPHA)* (1 - Math.abs(position));
view.setScaleY(scaleFactor);
view.setAlpha(alphaFactor);
} else if (position <= 1) { // (0,1]
// Fade the page out.
// Counteract the default slide transition
// Scale the page down (between MIN_SCALE and 1)
float scaleFactor = MIN_SCALE + (1 - MIN_SCALE)* (1 - Math.abs(position));
float alphaFactor = MIN_ALPHA + (1 - MIN_ALPHA)* (1 - Math.abs(position));
view.setScaleY(scaleFactor);
view.setAlpha(alphaFactor);
} else { // (1,+Infinity]
// This page is way off-screen to the right.
view.setScaleY(MIN_SCALE);
view.setAlpha(MIN_ALPHA);
}
}
}
给ViewPager设置PageTransformer
this.setPageTransformer(false, new DepthPageTransformer());
至此我们实现了带缩放的画廊效果,不过到这里有一个bug,在PageTransformer 中我们能操作的child只有两个,然而我们的效果却要求我们操作三个,这里bug截图,有兴趣的可以演示一下
这里解决问题开始想到在页面切换的时候判断左滑或者右滑来获取并缩放View,但实现起来有点麻烦,后来想到OnHierarchyChangeListener,通过监听ViewPager的添加和移除child来设置,代码如下
this.setOnHierarchyChangeListener(new OnHierarchyChangeListener() {
@Override
public void onChildViewAdded(View parent, View child) {
child.setScaleY(MIN_SCALE);
child.setAlpha(MIN_ALPHA);
}
@Override
public void onChildViewRemoved(View parent, View child) {
}
});
如此可以解决上面的bug,但是在滑动的时候总是有些卡顿,由于ViewPager的预加载导致下一个child显示的时候总是卡顿一下,无奈把ViewPager的预加载设置为了2
this.setOffscreenPageLimit(2); // 避免卡顿
后面无意间发现设置预加载2之后,上面的监听代码注释掉一样可以实现效果,如果哪位有更好的办法,欢迎告知。
完整代码Github地址:
https://github.com/liusai-90/GalleryView