高级UI晋升之常用View(三)中篇

更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680
本篇文章将从ViewPager来介绍常用View:
文章目录

一、简介

二、基本使用

  1. xml引用
  2. page布局
  3. 创建适配器
  4. 设置适配器
  5. 标题栏
    5.1. xml引用
    5.2. 重写PagerAdapter的getTitle()方法
  6. 翻页动画
    6.1. DepthPageTransformer
    6.2. ZoomOutPageTransformer
    6.3. 自定义动画
    6.4. 开源框架ViewPagerTransforms
  7. 翻页监听
    7.1. 设置方法
    7.2. 翻页监听接口
    7.3. 重写方法
    7.4. 使用

三、与Fragment结合使用

  1. 创建Fragment及相应的xml布局
  2. 给Viewpager设置数据和适配器

四、实现轮播图效果

  1. 特点
  2. 使用介绍
    2.1. 导包 + 权限
    2.2. xml引用
    2.3. 创建图片加载器
    2.4. 设置数据

五、实现画廊效果

  1. viewpager布局
  2. pager布局
  3. Adapter
  4. vp设置adapter
  5. 问题

一、简介

Viewpager,视图翻页工具,提供了多页面切换的效果。Android 3.0后引入的一个UI控件,位于v4包中。低版本使用需要导入v4包,但是现在我们开发的APP一般不再兼容3.0及以下的系统版本,另外现在大多数使用Android studio进行开发,默认导入v7包,v7包含了v4,所以不用导包,越来越方便了。

Viewpager使用起来就是我们通过创建adapter给它填充多个view,左右滑动时,切换不同的view。Google官方是建议我们使用Fragment来填充ViewPager的,这样 可以更加方便的生成每个Page,以及管理每个Page的生命周期。

Viewpager在Android开发中使用频率还是比较高的,下面开始一起学习吧!

二、基本使用

1. xml引用

<android.support.v4.view.ViewPager
    android:id="@+id/vp"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</android.support.v4.view.ViewPager>

2. page布局

<?xml version="1.0" encoding="utf-8"?>
<TextView
    android:id="@+id/tv"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FAE8DA"
    android:gravity="center"
    android:text="Hello"
    android:textSize="22sp">
</TextView>

3. 创建适配器

可直接new PagerAdapter,亦可创建它的子类

public class MyPagerAdapter extends PagerAdapter {
    private Context mContext;
    private List<String> mData;

    public MyPagerAdapter(Context context ,List<String> list) {
        mContext = context;
        mData = list;
    }

    @Override
    public int getCount() {
        return mData.size();
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        View view = View.inflate(mContext, R.layout.item_base,null);
        TextView tv = (TextView) view.findViewById(R.id.tv);
        tv.setText(mData.get(position));
        container.addView(view);
        return view;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        // super.destroyItem(container,position,object); 这一句要删除,否则报错
        container.removeView((View)object);
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }
}

4. 设置适配器

private void setVp() {
    List<String> list = new ArrayList<>();
    for (int i = 0; i < 3; i++) {
       list.add("第"+i+"个View");
    }

    ViewPager vp = (ViewPager) findViewById(R.id.vp);
    vp.setAdapter(new MyPagerAdapter(this,list));
}

效果:

 
19956127-6ca743fba39f74a2.gif
20180228134517956.gif

5. 标题栏

给Viewpager设置标题栏有一下几种方式:

PagerTabStrip: 带有下划线
PagerTitleStrip: 不带下划线
TabLayout:5.0后推出
TabLayout的详细使用,可以看我的另一篇文章TabLayout。

下面介绍另外两个的使用方法,没什么区别:

1. xml引用

<android.support.v4.view.ViewPager
    android:id="@+id/vp"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.view.PagerTitleStrip
        android:id="@+id/pager_title"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:background="@android:color/white"
        android:layout_gravity="top"
        android:textColor="#ff0000"
        android:textSize="18sp">

    </android.support.v4.view.PagerTitleStrip>

</android.support.v4.view.ViewPager>
 <android.support.v4.view.ViewPager
    android:id="@+id/vp"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v4.view.PagerTabStrip
        android:id="@+id/pager_tab"
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:layout_gravity="top"
        android:background="@android:color/white"
        android:textColor="#ff0000">
    </android.support.v4.view.PagerTabStrip>
    
</android.support.v4.view.ViewPager>
  1. 重写PagerAdapter的getTitle()方法
 @Override
public CharSequence getPageTitle(int position) {
    return mTitles[position];
}

这两种方法作为了解,不常用,项目中还没用到过

效果:

 
19956127-4f77f47bef964456.gif
 
 
19956127-f0bd1e3f4e0a3e73.gif
 

6. 翻页动画

ViewPager有个方法叫做:

setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) 用于设置ViewPager切换时的动画效果,并且google官方还给出了两个示例(因为使用的是属性动画,所以不兼容3.0以下)。

1. DepthPageTransformer

public class DepthPageTransformer implements ViewPager.PageTransformer {
    private static final float MIN_SCALE = 0.75f;
    public void transformPage(View view, float position) {
        int pageWidth = view.getWidth();
        if (position < -1) { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0);
        } else if (position <= 0) { // [-1,0]
            // Use the default slide transition when moving to the left page
            view.setAlpha(1);
            view.setTranslationX(0);
            view.setScaleX(1);
            view.setScaleY(1);
        } else if (position <= 1) { // (0,1]
            // Fade the page out.
            view.setAlpha(1 - position);
            // Counteract the default slide transition
            view.setTranslationX(pageWidth * -position);
            // Scale the page down (between MIN_SCALE and 1)
            float scaleFactor = MIN_SCALE
                    + (1 - MIN_SCALE) * (1 - Math.abs(position));
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);
        } else { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0);
        }
    }
}

调用:

vp.setPageTransformer(false,new DepthPageTransformer());

效果:

 
19956127-a14cce3249e1b894.gif
 

2. ZoomOutPageTransformer

public class ZoomOutPageTransformer implements ViewPager.PageTransformer
{
    private static final float MIN_SCALE = 0.85f;
    private static final float MIN_ALPHA = 0.5f;
    @SuppressLint("NewApi")
    public void transformPage(View view, float position)
    {
        int pageWidth = view.getWidth();
        int pageHeight = view.getHeight();
        Log.e("TAG", view + " , " + position + "");
        if (position < -1)
        { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setAlpha(0);
        } else if (position <= 1) //a页滑动至b页 ; a页从 0.0 -1 ;b页从1 ~ 0.0
        { // [-1,1]
            // Modify the default slide transition to shrink the page as well
            float scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position));
            float vertMargin = pageHeight * (1 - scaleFactor) / 2;
            float horzMargin = pageWidth * (1 - scaleFactor) / 2;
            if (position < 0)
            {
                view.setTranslationX(horzMargin - vertMargin / 2);
            } else
            {
                view.setTranslationX(-horzMargin + vertMargin / 2);
            }
            // Scale the page down (between MIN_SCALE and 1)
            view.setScaleX(scaleFactor);
            view.setScaleY(scaleFactor);
            // Fade the page relative to its size.
            view.setAlpha(MIN_ALPHA + (scaleFactor - MIN_SCALE)
                    / (1 - MIN_SCALE) * (1 - MIN_ALPHA));
        } else
        { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setAlpha(0);
        }
    }
}

调用:

vp.setPageTransformer(false,new ZoomOutPageTransformer());

效果:

 
19956127-dd778f9e31a1d70a.gif
 

3. 自定义动画
网上看到鸿洋大神写的

public class RotateDownPageTransformer implements ViewPager.PageTransformer {
    private static final float ROT_MAX = 20.0f;
    private float mRot;


    public void transformPage(View view, float position)
    {
        Log.e("TAG", view + " , " + position + "");
        if (position < -1)
        { // [-Infinity,-1)
            // This page is way off-screen to the left.
            view.setRotation(0);
        } else if (position <= 1) // a页滑动至b页 ; a页从 0.0 ~ -1 ;b页从1 ~ 0.0
        { // [-1,1]
            // Modify the default slide transition to shrink the page as well
            if (position < 0)
            {
                mRot = (ROT_MAX * position);
                view.setPivotX(view.getMeasuredWidth() * 0.5f);
                view.setPivotY(view.getMeasuredHeight());
                view.setRotation( mRot);
            } else
            {
                mRot = (ROT_MAX * position);
                view.setPivotX(view.getMeasuredWidth() * 0.5f);
                view.setPivotY(view.getMeasuredHeight());
                view.setRotation( mRot);
            }
            // Scale the page down (between MIN_SCALE and 1)
            // Fade the page relative to its size.
        } else
        { // (1,+Infinity]
            // This page is way off-screen to the right.
            view.setRotation( 0);
        }
    }
}

效果:

 
19956127-e72543d3e1e3141b.gif
 

position说明:
当前显示页为0,前一页为-1,后一页为1,滑动过程中数值不断变大或变小,所以为float类型

4. 开源框架ViewPagerTransforms
里面有十几种翻页动画,基本够用了
Github地址:ViewPagerTransforms

7. 翻页监听

1. 设置方法

addOnPageChangeListener()

2. 翻页监听接口

ViewPager.OnPageChangeListener

3. 重写方法
onPageScrolled(int position, float positionOffset, int positionOffsetPixels)

页面滑动状态停止前一直调用

position:当前点击滑动页面的位置
positionOffset:当前页面偏移的百分比
positionOffsetPixels:当前页面偏移的像素位置

onPageSelected(int position)

滑动后显示的页面和滑动前不同,调用

position:选中显示页面的位置

onPageScrollStateChanged(int state)

页面状态改变时调用

state:当前页面的状态

SCROLL_STATE_IDLE:空闲状态
SCROLL_STATE_DRAGGING:滑动状态
SCROLL_STATE_SETTLING:滑动后滑翔的状态

  1. 使用
vp.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        Log.e("vp","滑动中=====position:"+ position + "   positionOffset:"+ positionOffset + "   positionOffsetPixels:"+positionOffsetPixels);
    }

    @Override
    public void onPageSelected(int position) {
        Log.e("vp","显示页改变=====postion:"+ position);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        switch (state) {
            case ViewPager.SCROLL_STATE_IDLE:
                Log.e("vp","状态改变=====SCROLL_STATE_IDLE====静止状态");
                break;
            case ViewPager.SCROLL_STATE_DRAGGING:
                Log.e("vp","状态改变=====SCROLL_STATE_DRAGGING==滑动状态");
                break;
            case ViewPager.SCROLL_STATE_SETTLING:
                Log.e("vp","状态改变=====SCROLL_STATE_SETTLING==滑翔状态");
                break;
        }
    }
});

Log:

 
19956127-bbdf47afddc70337.png
 

三、与Fragment结合使用

与Fragment结合使用其实也一样,只是用Fragment代替原先的View,填充Viewpager;然后就是Adapter不一样,配合Fragment使用的有两个Adapter:FragmentPagerAdapter和FragmentStatePagerAdapter。

相同点:
FragmentPagerAdapter和FragmentStatePagerAdapter都继承自PagerAdapter

不同点:
卸载不再需fragment时,各自采用的处理方法有所不同

FragmentStatePagerAdapter会销毁不需要的fragment。事务提交后, activity的FragmentManager中的fragment会被彻底移除。 FragmentStatePagerAdapter类名中的“state”表明:在销毁fragment时,可在onSaveInstanceState(Bundle)方法中保存fragment的Bundle信息。用户切换回来时,保存的实例状态可用来恢复生成新的fragment

FragmentPagerAdapter有不同的做法。对于不再需要的fragment, FragmentPagerAdapter会选择调用事务的detach(Fragment)方法来处理它,而非remove(Fragment)方法。也就是说, FragmentPagerAdapter只是销毁了fragment的视图, fragment实例还保留在FragmentManager中。因此,FragmentPagerAdapter创建的fragment永远不会被销毁

也就是:在destroyItem()方法中,FragmentStatePagerAdapter调用的是remove()方法,适用于页面较多的情况;FragmentPagerAdapter调用的是detach()方法,适用于页面较少的情况。但是有页面数据需要刷新的情况,不管是页面少还是多,还是要用FragmentStatePagerAdapter,否则页面会因为没有重建得不到刷新

使用如下:

1. 创建Fragment及相应的xml布局

public class PagerFragment extends Fragment {
    String mContent;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        mContent = (String) getArguments().get("content");
        View view = inflater.inflate(R.layout.fragment_pager, container, false) ;
        TextView textView = (TextView) view.findViewById(R.id.tv);
        textView.setText(mContent);
        return view;
    }

}
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.strivestay.viewpagerdemo.PagerFragment">

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textSize="18sp"
        android:text="@string/hello_blank_fragment"/>

</FrameLayout>

2. 给Viewpager设置数据和适配器

private void setVp() {
    final List<PagerFragment> list = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        PagerFragment fragment = new PagerFragment();
        Bundle bundle = new Bundle();
        bundle.putString("content","第"+i+"个Fragment");
        fragment.setArguments(bundle);

        list.add(fragment);
    }

    ViewPager vp = (ViewPager) findViewById(R.id.vp);
//        vp.setAdapter(new FragmentPagerAdapter(getSupportFragmentManager()) {
//            @Override
//            public Fragment getItem(int position) {
//                return list.get(position);
//            }
//
//            @Override
//            public int getCount() {
//                return list.size();
//            }
//        });

    vp.setAdapter(new FragmentStatePagerAdapter(getSupportFragmentManager()) {
        @Override
        public Fragment getItem(int position) {
            return list.get(position);
        }

        @Override
        public int getCount() {
            return list.size();
        }
    });

}

效果:

 
19956127-381f4b0c65a17b04.gif
 

四、实现轮播图效果

这里我就不自己实现了,介绍一个轮播图开源控件:banner

1. 特点

支持无限循环和多种主题
可以灵活设置轮播样式、动画、轮播和切换时间、位置、图片加载框架

2. 使用介绍

1. 导包 + 权限

implementation 'com.youth.banner:banner:1.4.10'
<!-- 加载网络图片需要权限 -->
<uses-permission android:name="android.permission.INTERNET" /> 

<!-- 加载本地图片需要权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

2. xml引用

<com.youth.banner.Banner
    android:id="@+id/banner"
    android:layout_width="match_parent"
    android:layout_height="自己设定"/>

3. 创建图片加载器

/**
 * 图片轮播加载
 */
public class GlideImageLoader extends ImageLoader {

    @Override  // path随便传,我这里最终传的是个对象,拿到图片Url
    public void displayImage(Context context, Object path, ImageView imageView) {

        //Glide 加载图片,Fresco也好、加载本地图片也好,这个类功能就是加载图片
        Glide.with(context).load(((AdList.DataBean)path).image).into(imageView);

    }
}

4. 设置数据

/**
 * 设置banner
 * @param data
 */
private void setBanner(List<AdList.DataBean> data) {
    // banner样式
    banner.setBannerStyle(BannerConfig.NOT_INDICATOR);
    // 设置图片加载器
    banner.setImageLoader(new GlideImageLoader());
    // 设置图片集合
    banner.setImages(data);
    // 翻页特效
    banner.setBannerAnimation(Transformer.Default);
    // 设置轮播时间
    banner.setDelayTime(4000);
    // banner设置方法全部调用完毕时最后调用
    banner.start();
}

@Override
public void onResume() {
    super.onResume();
    //开始自动翻页
    banner.startAutoPlay();
}

@Override
public void onPause() {
    super.onPause();
    //停止翻页
    banner.stopAutoPlay();
}

以上是简单使用,更详细的用法可以直接到GitHub上去看,文档是中文的,很方便,API也很简单,上面已经给出链接

效果:(项目里截图不方便,直接拿的示例图)

 
19956127-44ef007038f5f763.png
 

五、实现画廊效果

效果如下:

 
19956127-e76a947cefd188db.gif
 
 
19956127-634b82ce42e7d94b.gif
 

实现步骤:
1. viewpager布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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"
    android:orientation="vertical"
    android:clipChildren="false"
    tools:context="com.strivestay.viewpagerdemo.FourthActivity">

    <include layout="@layout/layout_toolbar"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/vp"
        android:layout_width="match_parent"
        android:layout_height="180dp"
        android:layout_marginTop="100dp"
        android:layout_marginLeft="30dp"
        android:layout_marginRight="30dp"
        android:clipChildren="false">
    </android.support.v4.view.ViewPager>

</LinearLayout>

要点:
给viewpager和它的父布局都设置属性android:clipChildren=“false”

. pager布局

item_banner_samll.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="200dp"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/iv_banner"
        android:layout_width="200dp"
        android:layout_height="180dp"
        android:background="#009999"
        android:scaleType="centerCrop"/>
</LinearLayout>

item_banner.xml

<?xml version="1.0" encoding="utf-8"?>
<ImageView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/iv_banner"
    android:layout_width="match_parent"
    android:layout_height="180dp"
    android:background="#009999"
    android:scaleType="centerCrop"/>

区别:
宽度,一个占满viewpager的宽度,一个小于viewpager的宽度

3. Adapter

/**
 * 画廊效果,page宽度占满vp
 * @author StriveStay
 * @date 2018/2/24
 */
public class FourthPageAdapter extends PagerAdapter {
    private Context mContext;

    public FourthPageAdapter(Context context) {
        mContext = context;
    }

    @Override
    public int getCount() {
        return Integer.MAX_VALUE;
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        // 就4张图片
        position %= 4;

        View view = View.inflate(mContext,R.layout.item_banner,null);
        ImageView iv = (ImageView) view.findViewById(R.id.iv_banner);
        int resourceId = mContext.getResources().getIdentifier("img" + (position + 1), "drawable", mContext.getPackageName());
        Glide.with(mContext).load(resourceId).into(iv);
        //            iv.setImageResource(resourceId);
        container.addView(view);
        return view;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((View)object);
    }

}

如果是page宽度 < vp宽度,需要重写getPageWidth()方法,用于计算page占据vp的百分比

@Override
public float getPageWidth(int position) {
    float itemWidth =  (mContext.getResources().getDisplayMetrics().density * 200);
    float vpWidth = (mContext.getResources().getDisplayMetrics().widthPixels - mContext.getResources().getDisplayMetrics().density * 60);
    return  itemWidth / vpWidth;
}

3. vp设置adapter

private void setVp() {
    ViewPager vp = (ViewPager) findViewById(R.id.vp);

    // 设置适配器
//        vp.setAdapter(new FourthPageAdapter(this));
    vp.setAdapter(new FourthSmallPageAdapter(this));

    // page 边距
    vp.setPageMargin((int)(getResources().getDisplayMetrics().density * 15));

    // 为了左右无限滑动,显示在中间,且显示第一张
    int i = Integer.MAX_VALUE/2%4;
    vp.setCurrentItem(Integer.MAX_VALUE/2 + (4-i));

}

4. 问题
当page宽度 < vp宽度,且page的数量较少,没有占满vp,这时滑动vp,会出现闪屏,如下:

 
19956127-1e7de8f0148ccd54.gif
 

解决办法:

当明确知道vp放不下2个page时,可以如下处理

@Override
public float getPageWidth(int position) {
    // 加上这句
    if(getCount() < 2){
        return super.getPageWidth(position);
    }

    float itemWidth =  (mContext.getResources().getDisplayMetrics().density * 200);
    float vpWidth = (mContext.getResources().getDisplayMetrics().widthPixels - mContext.getResources().getDisplayMetrics().density * 60);

    Log.e("比例",vpWidth+"==="+itemWidth+"==="+(int)(vpWidth/itemWidth));

    return  itemWidth / vpWidth;

}

当vp可以放置两个以上的page时,也是个通用的方法

vp.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // vp中最多能放下2个page,则让vp中page个数 < 3时,让vp不能滑动
        if(vp.getChildCount() < 3 && event.getAction() == MotionEvent.ACTION_MOVE){
            return true;
        }
        return false;
    }
});

更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680
原文链接:https://blog.csdn.net/weixin_39251617/article/details/79399592

原文地址:https://www.cnblogs.com/Android-Alvin/p/11953009.html