使用ViewPager+Fragment制作Material Design(MD)风格的炫酷引导页

我记得之前写过一篇关于ViewPager+Fragment制作引导页的Blog。

使用ViewPager+Fragment制作一个简单的引导页,外加一个简易的Indicator圆形指示器

这篇文章实现的效果还不错,但是肯定比不上今天的这篇文章中最后运行的效果。

让我们重新使用ViewPager+Fragment,来制作一个Material Design(MD)风格的炫酷引导页吧。

引导页,用少量的文字表达出明确的含义,这在引导用户领域是个很讨巧的方法。简单明了的三张图片,就把APP的主要作用功能全面的展示出来,会给用户留下一种操作起来一点也不复杂的第一印象,好像只需三步就能轻轻松松满足自己的需求。可爱的界面风格也很有针对性的引起了主要客户群的兴趣,为APP整体加分不少。

引导页面的主要作用是介绍APP的基本功能,根据基础功能进行操作演示也不失为引导用户的好方法。将使用方法直接用最简单的图示按顺序展示在用户面前,一定能让用户最快速的掌握。

首先就是要添加依赖了,在Module的build.gradle文件中,添加支持库的依赖。

dependencies {
    ......
    // design
    implementation 'com.android.support:design:28.0.0'
}

然后开始编写代码,步骤如下:

  1. 使用ViewPager + Fragment创建引导页(FragmentPagerAdapter,Fragment)
  2. 当页面滑动时,控制背景色的渐变(使用ArgbEvaluator用于更新颜色,在前一个页面和后一个页面的颜色之间进行过渡。)
  3. 当App第一次加载时,显示引导页(使用SharedPreferences保存标记)
    先看一下引导页面的布局文件activity_pager.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/cyan"
    tools:context=".PagerActivity">

    <android.support.v4.view.ViewPager
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="?attr/actionBarSize" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_gravity="bottom"
        android:layout_marginBottom="?attr/actionBarSize"
        android:alpha="0.12"
        android:background="@android:color/white" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:layout_gravity="bottom"
        android:paddingStart="@dimen/activity_horizontal_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingEnd="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin">

        <Button
            android:id="@+id/btn_skip"
            style="@style/Widget.AppCompat.Button.Borderless"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="start|center"
            android:text="@string/skip"
            android:textColor="@android:color/white" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:orientation="horizontal">

            <ImageView
                android:id="@+id/image_indicator_zero"
                android:layout_width="8dp"
                android:layout_height="8dp"
                android:layout_marginEnd="@dimen/activity_margin_half"
                android:layout_marginRight="@dimen/activity_margin_half"
                android:background="@drawable/indicator_unselected" />

            <ImageView
                android:id="@+id/image_indicator_one"
                android:layout_width="8dp"
                android:layout_height="8dp"
                android:layout_marginEnd="@dimen/activity_margin_half"
                android:layout_marginRight="@dimen/activity_margin_half"
                android:background="@drawable/indicator_unselected" />

            <ImageView
                android:id="@+id/image_indicator_two"
                style="@style/Widget.AppCompat.Button.Borderless"
                android:layout_width="8dp"
                android:layout_height="8dp"
                android:background="@drawable/indicator_unselected" />

        </LinearLayout>

        <Button
            android:id="@+id/btn_finish"
            style="@style/Widget.AppCompat.Button.Borderless"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end|center"
            android:text="@string/finish"
            android:textColor="@android:color/white"
            android:visibility="gone" />

        <ImageButton
            android:id="@+id/btn_next"
            style="@style/Widget.AppCompat.Button.Borderless"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end|center"
            android:padding="@dimen/activity_horizontal_margin"
            android:src="@drawable/ic_chevron_right_24dp"
            android:tint="@android:color/white" />

    </FrameLayout>

</android.support.design.widget.CoordinatorLayout>

然后就是引导页的Activity文件

import android.animation.ArgbEvaluator;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;

import com.yunyang.yunyangblogdemo.adapter.GuidePagerAdapter;
import com.yunyang.yunyangblogdemo.utils.Utils;

public class PagerActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "yunyang";

    private ViewPager mViewPager;
    private Button btn_skip;
    private ImageView image_indicator_zero;
    private ImageView image_indicator_one;
    private ImageView image_indicator_two;
    private Button btn_finish;
    private ImageButton mNextBtn;

    private GuidePagerAdapter mGuidePagerAdapter;

    private int lastLeftValue = 0;
    private ImageView[] indicators;
    // 当前页面位置
    private int currentPage = 0;

    // 是否首次进入引导页的标记
    private boolean isFirstGuide;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            // 沉浸式状态栏
            getWindow().getDecorView().setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                            | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
            // 更改状态栏颜色
            getWindow().setStatusBarColor(ContextCompat.getColor(this, R.color.black_trans80));
        }
        setContentView(R.layout.activity_pager);
        // 首次进入App,展示引导页。
        isFirstGuide = Utils.readSharedSetting(PagerActivity.this, "isGuide", false);
        if (isFirstGuide) {
            gotoMain();
        }
        initView();
        initMDViewPager();
    }

    /**
     * 跳转首页
     */
    private void gotoMain() {
        finish();
        Intent intent = new Intent(this, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
         /*
             注意:
            1、overridePendingTransition方法必须在startActivity()或者finish()方法的后面。
            2、如果参数是0,表示没有动画
            public void overridePendingTransition(int enterAnim, int exitAnim) {}
            在A启动B时:
            enterAnim:是B进入的动画
            exitAnim:是A退出的动画
             */
        overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
    }

    private void initMDViewPager() {
        mGuidePagerAdapter = new GuidePagerAdapter(getSupportFragmentManager());
        mNextBtn = (ImageButton) findViewById(R.id.btn_next);
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP)
            mNextBtn.setImageDrawable(
                    Utils.tintMyDrawable(ContextCompat.getDrawable(this, R.drawable.ic_chevron_right_24dp), Color.WHITE)
            );
        indicators = new ImageView[]{
                image_indicator_zero,
                image_indicator_one,
                image_indicator_two
        };
        mViewPager.setAdapter(mGuidePagerAdapter);
        mViewPager.setCurrentItem(currentPage);
        updateIndicators(currentPage);

        final int color1 = ContextCompat.getColor(this, R.color.cyan);
        final int color2 = ContextCompat.getColor(this, R.color.green);
        final int color3 = ContextCompat.getColor(this, R.color.orange);

        final int[] colorList = new int[]{color1, color2, color3};
        // 使用ArgbEvaluator用于更新颜色,在前一个页面和后一个页面的颜色之间进行过渡。
        final ArgbEvaluator evaluator = new ArgbEvaluator();

        mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                // 更新颜色
                int colorUpdate =
                        (Integer) evaluator.evaluate(
                                positionOffset,
                                colorList[position],
                                colorList[position == 2 ? position : position + 1]);
                mViewPager.setBackgroundColor(colorUpdate);
            }

            @Override
            public void onPageSelected(int position) {
                // 更新当前角标位置
                currentPage = position;
                updateIndicators(currentPage);

                switch (position) {
                    case 0:
                        mViewPager.setBackgroundColor(color1);
                        break;
                    case 1:
                        mViewPager.setBackgroundColor(color2);
                        break;
                    case 2:
                        mViewPager.setBackgroundColor(color3);
                        break;
                }

                btn_skip.setVisibility(position == 0 ? View.VISIBLE : View.GONE);
                btn_finish.setVisibility(position == 2 ? View.VISIBLE : View.GONE);
                mNextBtn.setVisibility(position == 2 ? View.GONE : View.VISIBLE);
            }

            @Override
            public void onPageScrollStateChanged(int i) {

            }
        });

    }

    /**
     * 通过切换两个不同的drawable来更新指示器。
     */
    private void updateIndicators(int position) {
        for (int i = 0; i < indicators.length; i++) {
            indicators[i].setBackgroundResource(
                    i == position ? R.drawable.indicator_selected : R.drawable.indicator_unselected
            );
        }
    }


    private void initView() {
        mViewPager = (ViewPager) findViewById(R.id.container);
        btn_skip = (Button) findViewById(R.id.btn_skip);
        image_indicator_zero = (ImageView) findViewById(R.id.image_indicator_zero);
        image_indicator_one = (ImageView) findViewById(R.id.image_indicator_one);
        image_indicator_two = (ImageView) findViewById(R.id.image_indicator_two);
        btn_finish = (Button) findViewById(R.id.btn_finish);
        mNextBtn = (ImageButton) findViewById(R.id.btn_next);

        btn_skip.setOnClickListener(this);
        btn_finish.setOnClickListener(this);
        mNextBtn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_skip:
                gotoMain();
                break;
            case R.id.btn_finish:
                Utils.saveSharedSetting(PagerActivity.this, "isGuide", true);
                gotoMain();
                break;
            case R.id.btn_next:
                currentPage += 1;
                if (currentPage > 2) {
                    currentPage = 2;
                }
                mViewPager.setCurrentItem(currentPage, true);
                break;
            default:
                break;
        }
    }
}

注释写的很清楚,看代码完全OK的呢。
然后就是ViewPager的适配器

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

import com.yunyang.yunyangblogdemo.fragment.GuideFragment;

/**
 * 作者    yunyang
 * 时间    2018/11/13 11:20
 * 文件    YunyangBlogDemo
 * 描述   引导页ViewPager的适配器
 */
public class GuidePagerAdapter extends FragmentPagerAdapter {

    public GuidePagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return GuideFragment.newInstance(position + 1);

    }

    @Override
    public int getCount() {
        return 3;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        switch (position) {
            case 0:
                return "PAGE ONE";
            case 1:
                return "PAGE TWO";
            case 2:
                return "PAGE THREE";
        }
        return null;
    }

}

以及ViewPager中包含的Fragment文件

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.yunyang.yunyangblogdemo.R;

/**
 * 作者    yunyang
 * 时间    2018/11/13 11:23
 * 文件    YunyangBlogDemo
 * 描述   引导页ViewPager中Fragment
 */
public class GuideFragment extends Fragment {

    private static final String ARG_SECTION_NUMBER = "guide_number";

    private ImageView mImageView;

    private int[] bgs = new int[]{
            R.drawable.ic_like_24dp,
            R.drawable.ic_house_24dp,
            R.drawable.ic_helper_24dp};

    public static GuideFragment newInstance(int sectionNumber) {
        GuideFragment fragment = new GuideFragment();
        Bundle args = new Bundle();
        args.putInt(ARG_SECTION_NUMBER, sectionNumber);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_pager, container, false);
        TextView textView = (TextView) view.findViewById(R.id.fragment_pager_text_label);
        textView.setText(getString(R.string.guide_format, getArguments().getInt(ARG_SECTION_NUMBER)));
        mImageView = (ImageView) view.findViewById(R.id.fragment_pager_img);
        mImageView.setBackgroundResource(bgs[getArguments().getInt(ARG_SECTION_NUMBER) - 1]);
        return view;
    }

}

GuideFragment的布局文件fragment_pager.xml

<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"
    android:padding="@dimen/activity_horizontal_margin">


    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/fragment_pager_img"
            android:layout_width="192dp"
            android:layout_height="192dp"
            android:layout_gravity="center"
            android:adjustViewBounds="true"
            android:alpha="0.30"
            android:background="@drawable/ic_like_24dp"
            android:scaleType="fitCenter" />

    </FrameLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:gravity="center"
        android:orientation="vertical"
        android:padding="@dimen/activity_horizontal_margin">

        <TextView
            android:id="@+id/fragment_pager_text_label"
            style="@style/TextAppearance.AppCompat.Headline"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            tools:text="Page One" />

        <TextView
            style="@style/TextAppearance.AppCompat.Body1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/activity_horizontal_margin"
            android:alpha="0.7"
            android:gravity="center"
            android:text="@string/important_feature_description"
            android:textColor="@android:color/white" />

    </LinearLayout>

</FrameLayout>

以及上面所用到的工具类Utils.java文件

import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.support.v4.graphics.drawable.DrawableCompat;

/**
 * 作者    yunyang
 * 时间    2018/11/13 11:39
 * 文件    YunyangBlogDemo
 * 描述   工具类
 */
public class Utils {

    private static final String PREFERENCES_FILE = "sp_settings";

    public static boolean readSharedSetting(Context context, String settingSPName, boolean defaultValue) {
        SharedPreferences sharedPref = context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
        return sharedPref.getBoolean(settingSPName, defaultValue);
    }

    public static void saveSharedSetting(Context context, String settingSPName, boolean settingValue) {
        SharedPreferences sharedPref = context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPref.edit();
        editor.putBoolean(settingSPName, settingValue);
        editor.apply();
    }

    public static Drawable tintMyDrawable(Drawable drawable, int color) {
        drawable = DrawableCompat.wrap(drawable);
        DrawableCompat.setTint(drawable, color);
        DrawableCompat.setTintMode(drawable, PorterDuff.Mode.SRC_IN);
        return drawable;
    }

}

以及最后引导页结束后跳转到的首页页面。
首页布局文件

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="@string/index"
        android:textColor="@color/accent_500"
        android:textSize="36sp"
        android:textStyle="bold" />

</RelativeLayout>

首页Activity文件

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.yunyang.yunyangblogdemo.utils.Utils;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

}

OK,代码编写完成后,运行一下,看一下效果图:
展示引导页面,点击“跳过”按钮,跳转到首页。


skip.gif

首次启动,展示引导页面,点击“跳转首页”,跳转到首页;第二次启动,直接加载首页页面。


首次加载,进入首页.gif

嗯,很完美嘛。

Demo源代码CSDN

Demo源代码Github

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  • 这是谷歌官方给我们提供的一个兼容低版本安卓设备的软件包,里面包囊了只有在安卓3.0以上可以使用的api。而view...
    Ten_Minutes阅读 5,726评论 1 19
  • ViewPager简介: ViewPager(android.support.v4.view.ViewPager)...
    Ruheng阅读 27,439评论 8 59
  • 1 写这篇博客的初衷 首先一句话概括:我想把这几个月做的事情记录下来,并且希望尽量详细,希望读者读了这篇文章能够知...
    格老子阅读 2,627评论 1 56
  • 感觉自从减肥开始以来,每天都觉得时间不够用,要做饭,要买菜,要运动,各种事宜,每天拿来闲逛手机的时间都感觉不太够用...
    雨宫萌阅读 319评论 0 0
  • 人活着,最重要的就是健康了,身体健康,心里健康,灵魂也要健康,作为女人除了没有其他的大病,内分泌也要健康,特别是妇...
    董氏膏药邱姐阅读 303评论 0 0