前段时间开通了自己的私有博客地址 韩海龙的博客 ,我会首先把好的博文发到上面,然后有时间再放到CSDN上面来!
好了,回到正题上来,最近看到有同事在用土巴兔这个app,看了里面的一些效果非常的不错,就试着模仿了一下,首先模仿的是土巴兔里面一个选择装修风格的效果,先看原版效果图如下:
这种效果实现的方式很多,比如HorizonalScrollView、Recyclerview(水平)、ViewPager等等,这里我使用ViewPager来高仿,为啥通过ViewPager来高仿呢,一是ViewPager提供了PageTransformer,很容易的实现切换动画效果,二是ViewPager很容易控制滑动选中的一项居中,这里先看下高仿后的效果图,效果还是很不错的:
要实现这种效果,核心知识点有如下几个:
android:clipChildren设置为false,意味着不限制子View在其范围内,也就是说子view可以超出父view的范围
通过PageTransformer来实现缩放动画
拦截点击事件的位置来实现点击切换viewpager,这个为什么要实现,后面再说
下面我们一步一步讲解来实现这个布局效果
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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:id="@+id/page_container" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:clipChildren="false" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".MainActivity" tools:showIn="@layout/activity_main"> <com.hhl.tubatu.ClipViewPager android:id="@+id/viewpager" android:layout_width="200dp" android:layout_height="200dp" android:layout_centerInParent="true" android:clipChildren="false" android:overScrollMode="never" /> </RelativeLayout>
上面的RelativeLayout和自定义的ClipViewPager都各自添加了一个属性 android:clipChildren=”false” ,clipChildren的意思是是否限制子View在其范围内,这个默认是true,也就是默认是限制子view在其范围的
mViewPager = (ClipViewPager) findViewById(R.id.viewpager); mViewPager.setPageTransformer(true, new ScalePageTransformer());
再来看ScalePageTransformer的实现,核心就是实现transformPage(View page, float position)这个方法
/** * Created by HanHailong on 15/9/27. */ public class ScalePageTransformer implements ViewPager.PageTransformer { public static final float MAX_SCALE = 1.2f; public static final float MIN_SCALE = 0.6f; @Override public void transformPage(View page, float position) { if (position < -1) { position = -1; } else if (position > 1) { position = 1; } float tempScale = position < 0 ? 1 + position : 1 - position; float slope = (MAX_SCALE - MIN_SCALE) / 1; //一个公式 float scaleValue = MIN_SCALE + tempScale * slope; page.setScaleX(scaleValue); page.setScaleY(scaleValue); } }
其实核心代码就是这个动画实现部分,这里设置了一个最大缩放和最小缩放比例,当处于最中间的view往左边滑动时,它的position值是小于0的,并且是越来越小,它右边的view的position是从1逐渐减小到0的。
import android.content.Context; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import com.hhl.tubatu.adapter.RecyclingPagerAdapter; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private ClipViewPager mViewPager; private TubatuAdapter mPagerAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); mViewPager = (ClipViewPager) findViewById(R.id.viewpager); mViewPager.setPageTransformer(true, new ScalePageTransformer()); findViewById(R.id.page_container).setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return mViewPager.dispatchTouchEvent(event); } }); mPagerAdapter = new TubatuAdapter(this); mViewPager.setAdapter(mPagerAdapter); initData(); } private void initData() { List<Integer> list = new ArrayList<>(); list.add(R.drawable.style_xiandai); list.add(R.drawable.style_jianyue); list.add(R.drawable.style_oushi); list.add(R.drawable.style_zhongshi); list.add(R.drawable.style_meishi); list.add(R.drawable.style_dzh); list.add(R.drawable.style_dny); list.add(R.drawable.style_rishi); //设置OffscreenPageLimit mViewPager.setOffscreenPageLimit(list.size()); mPagerAdapter.addAll(list); } public static class TubatuAdapter extends RecyclingPagerAdapter { private final List<Integer> mList; private final Context mContext; public TubatuAdapter(Context context) { mList = new ArrayList<>(); mContext = context; } public void addAll(List<Integer> list) { mList.addAll(list); notifyDataSetChanged(); } @Override public View getView(int position, View convertView, ViewGroup container) { ImageView imageView = null; if (convertView == null) { imageView = new ImageView(mContext); } else { imageView = (ImageView) convertView; } imageView.setTag(position); imageView.setImageResource(mList.get(position)); return imageView; } @Override public int getCount() { return mList.size(); } } }
这里需要将setOffscreenPageLimit的值设置成数据源的总个数,如果不加这句话,会导致左右切换异常;二是需要将整个页面的事件分发给ViewPager,不然的话只有ViewPager中间的view能滑动,其他的都不能滑动,这是肯定的,因为ViewPager总体布局就是中间那一块大小,其他的子布局都跑到ViewPager外面来了。三是你发现ViewPager加了setOnTouchListener方法后,滑动是可以了,但是点击左右两边不能切换,这里需要重写ViewPager的 dispatchTouchEvent 方法,下面看ClipViewPager代码:
package com.hhl.tubatu; import android.content.Context; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; /** * Created by HanHailong on 15/9/27. */ public class ClipViewPager extends ViewPager { public ClipViewPager(Context context) { super(context); } public ClipViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_UP) { View view = viewOfClickOnScreen(ev); if (view != null) { setCurrentItem(indexOfChild(view)); } } return super.dispatchTouchEvent(ev); } /** * @param ev * @return */ private View viewOfClickOnScreen(MotionEvent ev) { int childCount = getChildCount(); int[] location = new int[2]; for (int i = 0; i < childCount; i++) { View v = getChildAt(i); v.getLocationOnScreen(location); int minX = location[0]; int minY = getTop(); int maxX = location[0] + v.getWidth(); int maxY = getBottom(); float x = ev.getX(); float y = ev.getY(); if ((x > minX && x < maxX) && (y > minY && y < maxY)) { return v; } } return null; } }
实现原理就是手指点击屏幕,如果点击的位置恰好落在ViewPager某个子View范围内,就让ViewPager切换到哪个子View! viewOfClickOnScreen 方法是获取手指点击ViewPager中的哪个子View,最后调用setCurrentItem切换到相应的子View,经过以上设置就大功告成了!
补充一下, RecyclingPagerAdapter 是 Jake Wharton Android大神封装的可用于复用的PagerAdapter。
最后附上源码 github