精华内容
参与话题
问答
  • Flutter 启动页 消除白屏

    万次阅读 2019-07-09 16:36:52
    文章目录Android 启动页IOS 任何一个app基本都会设计一个启动页,今天我们就来看看怎么在flutter项目中设置启动页。 开始的时候我的第一个想法就是把启动页当成一个Widget,先加载这个Widget然后在显示我们的主...


    任何一个app基本都会设计一个启动页,今天我们就来看看怎么在flutter项目中设置启动页。
    开始的时候我的第一个想法就是把启动页当成一个Widget,先加载这个Widget然后在显示我们的主页面,可是在实践的过程中遇到一个很大的问题,app启动的时候会出现较长时间的白屏,在android手机上很明显,而且性能越差时间越长,ios相对好些。做过android原生开发的都知道android原生也有白屏问题,不过flutter要明显的多,如何解决这个问题?其实我们在创建flutter项目的时候,系统已经帮我们解决了,只不过我们没有注意到而已。

    Android 启动页

    首先打开如下图的文件:
    在这里插入图片描述
    有这么一段
    在这里插入图片描述
    meta-data 中value设置为true代表有启动页,然后打开res->values->style.xml文件如下图:
    在这里插入图片描述
    文件中设置了style,名称为LaunchTheme,注意这个名称不能修改,style加载的是drawable中的launch_background.xml,我们打开这个文件:
    在这里插入图片描述

    默认情况下红框内的item是没有的,这就是我们要设置的启动页的图片,启动页的图片我们存放在drawable下,如下图:
    在这里插入图片描述
    到这里android启动页就是设置完成了。

    IOS

    打开如下目录:
    在这里插入图片描述
    将LaunchImage.png、LaunchImage@2x.png、LaunchImage@3x.png 替换为我们自己启动页图片即可。

    Flutter 系列文章

    展开全文
  • 前言

    前言

    在之前的文章中(Qt for Android Splash启动页最简单延时关闭)介绍了如何在 Qt 开发的 android 应用中添加启动页,那里介绍的方法是直接给启动页设置一张图片来显示,但是这样会带来一个问题,如果的尺寸是固定的,然而如今市场上 android 设备的分辨率比例格式太多,如果通过这种方式来设置启动页,那么在不同设备上将会出现不适配的问题,可能出现不同程度的拉伸,这种用户体验显然是很差的,所以这里要介绍的是自定义启动页的方式来解决启动页拉伸的问题。

    正文

    创建布局文件

    首先,我们需要创建一个 android 的布局文件 xml,在res/drawable目录下创建一个 xml 文件,并命名为background_splash.xml。
    然后,打开该文件,并输入:

    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        <item
            android:drawable="@color/colorStart"/>
        <item>
            <bitmap
                android:gravity="center"
                android:src="@mipmap/ic_launcher"/>
        </item>
    </layer-list>
    

    先是创建一个纯色背景,然后在背景上添加一张logo 图片来代表启动页。
    其中这里颜色值colorStart是在 colors.xml 中定义的

    <color name="colorStart">#2964be</color>
    

    ic_launcher是我们启动需要的 logo 图片,存放在mipmap文件夹下。
    如果对原生 android 开发布局熟悉的话,可以将页面做得更漂亮些,这里只是为了演示,做了一个简单的效果。

    然后在AndroidMenifest.xml中将启动页设置为我们定义好的布局文件background_splash:

    <meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/background_splash"/>
    

    效果图如下:

    可以稍微做得好看些,比如加个渐变色,然后将 logo 图标网上移动一点,
    修改启动页 xml 文件

    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
        <item>
            <shape >
                <gradient
                android:angle="-90"
                android:endColor="@color/colorEnd"
                android:startColor="@color/colorStart" />
            </shape>
        </item>
        <item android:left="10dip" android:top="200dip">
            <bitmap android:src="@mipmap/ic_launcher"
            android:gravity="center_horizontal|top"/>
        </item>
    </layer-list>
    

    效果图如下:

    更多效果可以慢慢去研究啦,这里只是做简单的演示,掌握这种方法就可以扩展啦。

    展开全文
  • 转载请注明出处(万分感谢!...ViewPager 从入门到带你撸个启动页之ViewPager基础入门(一) ViewPager 从入门到带你撸个启动页之Fragment+ViewPager(二) ViewPager 从入门到带你撸个启动页之实战启动页(三)一、

    转载请注明出处(万分感谢!):
    http://blog.csdn.net/javazejian/article/details/52144889

    关联文章:

    ViewPager 从入门到带你撸个启动页之ViewPager基础入门(一)

    ViewPager 从入门到带你撸个启动页之Fragment+ViewPager(二)

    ViewPager 从入门到带你撸个启动页之实战启动页(三)

    ViewPager 从入门到带你撸个启动页之实战PageTransformer切换动画特效(四)

    一、启动页实现概述

      通过前两篇的分享,我们已经基本掌握了ViewPager的基本使用,本篇咱们就来实现一个通用的启动页,先来看看启动页效果:

      看来效果还不错,但实际上只用了ViewPager,并没有用到Fragment,因为移动就是3张图片而已,所以PagerAdapter的实现类代码也是相当简单的。唯一比较棘手的是页面移动过程中底部的3个小点也必须跟着移动,这点需要配合ViewPager的滑动监听动态实时小圆点的移动位置。下面我们就来分析它的实现过程吧。

    二、启动页实现过程

    2.1 实现整体滑动效果

      首先我们先来实现整体的滑动效果,也就是添加3张图片作为ViewPager的滑动页面,因此我们的activity_guide.xml如下:

    <?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.support.v4.view.ViewPager
            android:id="@+id/vp_guide"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" >
        </android.support.v4.view.ViewPager>
    
    </RelativeLayout>

    接下来我们创建GuideActivity.java:

    package com.zejian.commonstartpage;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.support.v4.view.PagerAdapter;
    import android.support.v4.view.ViewPager;
    import android.view.View;
    import android.view.ViewGroup;
    import android.view.Window;
    import android.widget.ImageView;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Created by zejian
     * Time 16/8/7.
     * Description:
     */
    public class GuideActivity extends Activity{
        // ViewPager的数据
        private List<ImageView> imageViewList;
        ViewPager mViewPager;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // 去标题, 需要在setContentView方法之前调用
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            setContentView(R.layout.activity_guide);
            initView();// 初始化控件
        }
    
        /**
         * 初始化控件
         */
        private void initView() {
            mViewPager = (ViewPager) findViewById(R.id.vp_guide);
            GuideAdapter adapter = new GuideAdapter();
            mViewPager.setAdapter(adapter);
    }
    
        /**
         * TODO:初始化ViewPager数据 void
         */
        private void initData() {
            int[] imageResIDs = {R.drawable.guide_1, R.drawable.guide_2,
                    R.drawable.guide_3};
            imageViewList = new ArrayList<>();
    
            ImageView iv;// 图片
    
            for (int i = 0; i < imageResIDs.length; i++) {
                iv = new ImageView(this);
                iv.setBackgroundResource(imageResIDs[i]);
                imageViewList.add(iv);
            }
        }
    
    
    
        class GuideAdapter extends PagerAdapter {
    
            @Override
            public int getCount() {
                return imageViewList.size();
            }
    
            @Override
            public boolean isViewFromObject(View view, Object object) {
                return view == object;
            }
    
            /*
             * 删除元素
             */
            @Override
            public void destroyItem(ViewGroup container, int position, Object object) {
                container.removeView((View) object);
            }
    
            @Override
            public Object instantiateItem(ViewGroup container, int position) {
                ImageView iv = imageViewList.get(position);
                container.addView(iv);// 1. 向ViewPager中添加一个view对象
                return iv; // 2. 返回当前添加的view对象
            }
        }
    }

      我们简单说一下代码,首先我们通过initData方法去创建我们所需需要的界面,因为我们的界面都是图片,所以这里我们直接new出3个ImageView控件,并设置图片即可,最后我们把其添加到imageViewList数据集合中,到此数据初始化完成:

     /**
    * 初始化ViewPager数据 
    */
    private void initData() {
       int[] imageResIDs = {R.drawable.guide_1, R.drawable.guide_2,
               R.drawable.guide_3};
       imageViewList = new ArrayList<>();
    
       ImageView iv;// 图片
    
       for (int i = 0; i < imageResIDs.length; i++) {
           iv = new ImageView(this);
           iv.setBackgroundResource(imageResIDs[i]);
           imageViewList.add(iv);
       }
    }

      接着我们创建GuideAdapter数据适配器继承自PagerAdapter,重写其方法,返回我们所需要的界面,这个前两篇已经说得很清楚了,这里不过多分析了。最后我们把数据适配器GuideAdapter设置给ViewPager即可:

    mViewPager = (ViewPager) findViewById(R.id.vp_guide);
    GuideAdapter adapter = new GuideAdapter();
    mViewPager.setAdapter(adapter);

    到此启动页初步实现完成,我们运行一下,看看效果。

    不错!预期效果已经开始出现。接下来我们先把最后一页的按钮添加上。

    2.2 添加按钮

      需要添加按钮的话我们就必须修改activity_guide.xml文件,添加一个按钮如下:

    <?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.support.v4.view.ViewPager
            android:id="@+id/vp_guide"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" >
        </android.support.v4.view.ViewPager>
    
        <Button
            android:id="@+id/btn_guide_start_experience"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:layout_marginBottom="60dip"
            android:background="@drawable/button_bg"
            android:textColor="@drawable/button_textcolor"
            android:text="开始体验"
            android:textSize="20sp" 
            android:paddingLeft="20dip"
            android:paddingRight="20dip"
            android:paddingTop="5dip"
            android:paddingBottom="5dip"
            android:visibility="gone"
            />
    </RelativeLayout>

      显然我们添加了一个Button,并将其设置在父布局的底部而且水平居中,注意此时按钮先设置为gone,待需要显示时再设置其可见。然后我们在Activity_Guide.java中添加如下代码:

    btnStartExperience = (Button) findViewById(R.id.btn_guide_start_experience)
    //设置监听器
    btnStartExperience.setOnClickListener(this);

      设置完按钮的点击事件后,我们还需要解决一个问题,那就是当需要显示Button时再显示出来,很显然,我们只需在第3页时才需要显示按钮,这时我们就需要借助ViewPager的滑动事件来处理了,因此我们实现ViewPager.OnPageChangeListener接口,并实现其方法如下:

        /**
         * 当页面正在滚动时 position 当前选中的是哪个页面 positionOffset 比例 positionOffsetPixels 偏移像素
         */
        @Override
        public void onPageScrolled(int position, float positionOffset,
                                   int positionOffsetPixels) {
        }
    
        /**
         * 当页面被选中
         */
        @Override
        public void onPageSelected(int position) {
    
        }
    
        /**
         * 当页面滚动状态改变
         */
        @Override
        public void onPageScrollStateChanged(int state) {
    
        }

    简单介绍一下三个方法:

    • onPageScrolled(int position, float positionOffset,
      int positionOffsetPixels)

      当页面正在滚动时被回调。
      position 表示当前选中的是第几个页面;
      positionOffset 是当前页面滑动比例,其值得范围是[0,1),如果页面向右翻动,这个值不断变大,最后在趋近1的情况后突变为0。如果页面向左翻动,这个值不断变小,最后变为0。
      positionOffsetPixels 是当前页面滑动像素的偏移距离

    • onPageScrollStateChanged(int state)
      当页面滚动状态改变时被回调。有三个值:0(END),1(PRESS) , 2(UP) 。
      当用手指滑动翻页时,手指按下去的时候会触发这个方法,state值为1,手指抬起时,如果发生了滑动(即使很小),其值会变为2,最后变为0 。总共执行这个方法三次。当当前页面翻页时,会执行这个方法两次,state值分别为2 , 0 。

    • onPageSelected(int position)
      当页面被选中时回调,position表示被选中的页面,position下标从0开始计算。

      很显然,我们应该利用onPageSelected(int position)决定实现我们的Button是否展示,当position为最后一个页面时展示,其他时候都隐藏即可。代码如下:

     /**
     * 当页面被选中
     */
    @Override
    public void onPageSelected(int position) {
        // 显示体验按钮
        if (position == imageViewList.size() - 1) {
            // 显示
            btnStartExperience.setVisibility(View.VISIBLE);
        } else {
            btnStartExperience.setVisibility(View.GONE);// 隐藏
        }
    }

    到此按钮也集成完,我们看看整体GuideActivity代码:

    
    package com.zejian.commonstartpage;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.support.v4.view.PagerAdapter;
    import android.support.v4.view.ViewPager;
    import android.view.View;
    import android.view.ViewGroup;
    import android.view.Window;
    import android.widget.Button;
    import android.widget.ImageView;
    import android.widget.Toast;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Created by zejian
     * Time 16/8/7.
     * Description:
     */
    public class GuideActivity extends Activity implements ViewPager.OnPageChangeListener, View.OnClickListener {
        // ViewPager的数据
        private List<ImageView> imageViewList;
        // 开始体验按钮
        private Button btnStartExperience;
        // ViewPager
        ViewPager mViewPager;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // 去标题, 需要在setContentView方法之前调用
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            setContentView(R.layout.activity_guide);
            initView();// 初始化控件
        }
    
        /**
         * 初始化控件
         */
        private void initView() {
            mViewPager = (ViewPager) findViewById(R.id.vp_guide);
            btnStartExperience = (Button) findViewById(R.id.btn_guide_start_experience);
            initData();// 初始化ViewPager数据
            GuideAdapter adapter = new GuideAdapter();
            mViewPager.setAdapter(adapter);
            mViewPager.setOnPageChangeListener(this);// 设置监听器
            btnStartExperience.setOnClickListener(this);// 按钮添加监听
        }
    
        /**
         * TODO:初始化ViewPager数据 void
         */
        private void initData() {
            int[] imageResIDs = {R.drawable.guide_1, R.drawable.guide_2,
                    R.drawable.guide_3};
            imageViewList = new ArrayList<>();
    
            ImageView iv;// 图片
    
            for (int i = 0; i < imageResIDs.length; i++) {
                iv = new ImageView(this);
                iv.setBackgroundResource(imageResIDs[i]);
                imageViewList.add(iv);
            }
        }
    
    
        /**
         * 当页面正在滚动时 position 当前选中的是哪个页面 positionOffset 比例 positionOffsetPixels 偏移像素
         */
        @Override
        public void onPageScrolled(int position, float positionOffset,
                                   int positionOffsetPixels) {
        }
    
        /**
         * 当页面被选中
         */
        @Override
        public void onPageSelected(int position) {
            // 显示体验按钮
            if (position == imageViewList.size() - 1) {
                btnStartExperience.setVisibility(View.VISIBLE);// 显示
            } else {
                btnStartExperience.setVisibility(View.GONE);// 隐藏
            }
        }
    
        /**
         * 当页面滚动状态改变
         */
        @Override
        public void onPageScrollStateChanged(int state) {
    
        }
    
        /**
         * 打开新的界面
         */
        @Override
        public void onClick(View v) {
            Toast.makeText(getApplicationContext(), "跳转新界面", Toast.LENGTH_SHORT).show();
        }
    
    
        class GuideAdapter extends PagerAdapter {
    
            @Override
            public int getCount() {
                return imageViewList.size();
            }
    
            @Override
            public boolean isViewFromObject(View view, Object object) {
                return view == object;
            }
    
            /*
             * 删除元素
             */
            @Override
            public void destroyItem(ViewGroup container, int position, Object object) {
                container.removeView((View) object);
            }
    
            @Override
            public Object instantiateItem(ViewGroup container, int position) {
                ImageView iv = imageViewList.get(position);
                container.addView(iv);// 1. 向ViewPager中添加一个view对象
                return iv; // 2. 返回当前添加的view对象
            }
        }
    }

    我们运行看看其效果:

    2.3 添加指示器

      终于到最后一步了,也是比较麻烦的一步,为每一个页面添加指示器,而且指示器必须跟随页面的移动而移动。那就先从布局开始吧,我们在activity_guide.xml文件最下方添加如下代码:

    <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:layout_marginBottom="30dip">
    
            <LinearLayout
                android:id="@+id/ll_guide_point_group"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="horizontal"></LinearLayout>
    
            <View
                android:id="@+id/select_point"
                android:layout_width="10px"
                android:layout_height="10px"
                android:background="@drawable/point_select" />
    </RelativeLayout>

      我们在布局中添加了一个相对布局,其中包含了一个id为ll_guide_point_group的LinearLayout,这个线性布局主要用于存放指示器列表也就是3个灰色圆点,而另外一个id为select_point的View则是表示一个被选中的红色圆点。接下来我们就先来初始化这些点列表指示器的数据,因为灰色圆点的数量跟启动页面的数量是一致的,所以有多少个页面就有多少个灰色圆点,因此我们只需在生成图片时同时生成灰色圆点即可,每个点的宽高都为10,相隔距离也为10,代码如下:

    private void initData() {
            int[] imageResIDs = { R.drawable.guide_1, R.drawable.guide_2,
                    R.drawable.guide_3 };
            imageViewList = new ArrayList<ImageView>();
    
            ImageView iv;// 图片
            View view;// 点
            LayoutParams params; // 参数类
    
            for (int i = 0; i < imageResIDs.length; i++) {
                iv = new ImageView(this);
                iv.setBackgroundResource(imageResIDs[i]);
                imageViewList.add(iv);
                // 根据图片的个数, 每循环一次向LinearLayout中添加一个点
                view = new View(this);
                view.setBackgroundResource(R.drawable.point_normal);
                // 设置参数
                params = new LayoutParams(10, 10);
                if (i != 0) {
                    params.leftMargin = 10;
                }
                view.setLayoutParams(params);// 添加参数
                llPointGroup.addView(view);
            }
        }

      从代码我们可以看出,根据图片的个数, 每循环一次向LinearLayout(llPointGroup)中添加一个点,最后便可以生成与图片数量相同的灰色圆点。现在我们来运行一下,看看点的效果:

      嗯,小圆点已经出现,接下来就是如何移动的问题了。现在我们需要实现的效果是这样的,当我们滑动页面时,红色小圆点也跟着移动,例如当页面滑动到屏幕的一半时,红色小圆点也应处于灰色圆点的中间位置。如下图:

      因此,我们去动态计算页面滑动的比例,然后按相同比例去计算红色小圆点需要滑动的距离。比如当第1页往第0页滑动到中间时,此时小红点的滑动距离应该如下计算:

      我们假设屏幕宽度为320px,那么当第1页滑动到屏幕中间时,其滑动距离就是160,此时滑动比较就是160/320=0.5,也就是说红色小圆点的滑动比例也是0.5,通过这个滑动比例我们就可以计算出两个点间的滑动距离,我们可以通过以下方式计算出小红点要滑动的距离:
    pWidth=llPointGroup.getChildAt(1).getLeft()-llPointGroup.getChildAt(0).getLeft()
      最后乘以滑动比例就是小红点的滑动距离了。当然这只是其中一次滑动的完整计算过程,页面滑动过程中的小红点变化的计算方式也是相同(因为滑动过程中会多次调用onPageScrolled方法后面会说明),现在剩下的问题是我们如何知道页面滑动过程呢?还记得前面我们ViewPager设置的OnPageChangeListener监听器吧?其中有个回调方法onPageScrolled(int position, float positionOffset, int positionOffsetPixels)的参数positionOffset不就是页面的滑动比例吗?确实如此,因此我们可以利用这个滑动比例来动态计算小红点的滑动距离,这样也就可以达到页面滑动的过程中小红点也跟随移动的效果。下面我们给出实现代码:

    /**
    * 当页面正在滚动时 position 当前选中的是哪个页面 positionOffset 比例 positionOffsetPixels 偏移像素
    */
    @Override
    public void onPageScrolled(int position, float positionOffset,
                              int positionOffsetPixels) {
       //获取两个点间的距离,获取一次即可
       if(pWidth==0) {
           pWidth = llPointGroup.getChildAt(1).getLeft()
                   - llPointGroup.getChildAt(0).getLeft();
       }
    
       // 获取点要移动的距离
       int leftMargin = (int) (pWidth * (position + positionOffset));
       // 给红点设置参数
       RelativeLayout.LayoutParams params = (android.widget.RelativeLayout.LayoutParams) mSelectPointView
               .getLayoutParams();
       params.leftMargin = leftMargin;
       mSelectPointView.setLayoutParams(params);
    }

    这里我们再来简单分析一下计算移动距离的代码:

    // 获取点要移动的距离
    int leftMargin = (int) (pWidth * (position + positionOffset));

      由于position表示当前页面的下标,取值范围在[0,2],而positionOffset则表示当前界面的滑动比例,取值范围在[0,1),当页面向右滑动,positionOffset值不断变大,最后在趋近1的情况后突变为0。如果页面向左滑动,这个值不断变小,最后变为0,当positionOffset突变为0时则说明页面已处于展示位置。因此当我们把当前页面往左滑动时,当前position=0,而positionOffset则在不断的增大,我们过Log来观察一下

    当第0页滑动到左侧,而第1页正好在中间展示时,position=1,而positionOffset=0;

      因此我们可以通过positionOffset的值来计算小红点的滑动距离,当当前页面往左滑动时,positionOffset不断增大,此时由于position=0;所以pWidth * (position + positionOffset)也在不断增大,计算出小红点的移动距离leftMargin后,我们只需动态设置其params.leftMargin参数即可,这也就达到了小红点跟随移动的目的(onPageScrolled函数在移动中多次被触发),最终position=1,positionOffset=0,小红点也刚好落在了第2个灰色小圆点上。往后第3个点原理也是一样的,向右移动也依次类推即可。。
    最后我们给出GuideActivity的代码:

    package com.zejian.commonstartpage;
    
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.support.v4.view.PagerAdapter;
    import android.support.v4.view.ViewPager;
    import android.util.Log;
    import android.view.View;
    import android.view.ViewGroup;
    import android.view.Window;
    import android.widget.Button;
    import android.widget.ImageView;
    import android.widget.LinearLayout;
    import android.widget.LinearLayout.LayoutParams;
    import android.widget.RelativeLayout;
    import android.widget.Toast;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Created by zejian
     * Time 16/8/7.
     * Description:
     */
    public class GuideActivity extends Activity implements ViewPager.OnPageChangeListener, View.OnClickListener {
        // ViewPager的数据
        private List<ImageView> imageViewList;
        // 点的组
        private LinearLayout llPointGroup;
        // 选中的点view对象
        private View mSelectPointView;
        // 开始体验按钮
        private Button btnStartExperience;
        // ViewPager
        ViewPager mViewPager;
        // 点之间的宽度
        private int pWidth;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // 去标题, 需要在setContentView方法之前调用
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            setContentView(R.layout.activity_guide);
            initView();// 初始化控件
        }
    
        /**
         * 初始化控件
         */
        private void initView() {
            mViewPager = (ViewPager) findViewById(R.id.vp_guide);
            btnStartExperience = (Button) findViewById(R.id.btn_guide_start_experience);
            llPointGroup = (LinearLayout) findViewById(R.id.ll_guide_point_group);
            mSelectPointView = findViewById(R.id.select_point);
            initData();// 初始化ViewPager数据
            GuideAdapter adapter = new GuideAdapter();
            mViewPager.setAdapter(adapter);
            mViewPager.setOnPageChangeListener(this);// 设置监听器
            btnStartExperience.setOnClickListener(this);// 按钮添加监听
        }
    
        /**
         * TODO:初始化ViewPager数据 void
         */
        private void initData() {
            int[] imageResIDs = {R.drawable.guide_1, R.drawable.guide_2,
                    R.drawable.guide_3};
            imageViewList = new ArrayList<>();
    
            ImageView iv;// 图片
            View view;// 点
            LayoutParams params; // 参数类
    
            for (int i = 0; i < imageResIDs.length; i++) {
                iv = new ImageView(this);
                iv.setBackgroundResource(imageResIDs[i]);
                imageViewList.add(iv);
                // 根据图片的个数, 每循环一次向LinearLayout中添加一个点
                view = new View(this);
                view.setBackgroundResource(R.drawable.point_normal);
                // 设置参数
                params = new LayoutParams(10, 10);
                if (i != 0) {
                    params.leftMargin = 10;
                }
                view.setLayoutParams(params);// 添加参数
                llPointGroup.addView(view);
            }
        }
    
    
        /**
         * 当页面正在滚动时 position 当前选中的是哪个页面 positionOffset 比例 positionOffsetPixels 偏移像素
         */
        @Override
        public void onPageScrolled(int position, float positionOffset,
                                   int positionOffsetPixels) {
    
            Log.e("zejian","positionOffset:-->"+positionOffset);
            Log.e("zejian","position:-->"+position);
            //获取两个点间的距离,获取一次即可
            if(pWidth==0) {
                pWidth = llPointGroup.getChildAt(1).getLeft()
                        - llPointGroup.getChildAt(0).getLeft();
            }
    
            // 获取点要移动的距离
            int leftMargin = (int) (pWidth * (position + positionOffset));
            // 给红点设置参数
            RelativeLayout.LayoutParams params = (android.widget.RelativeLayout.LayoutParams) mSelectPointView
                    .getLayoutParams();
            params.leftMargin = leftMargin;
            mSelectPointView.setLayoutParams(params);
        }
    
        /**
         * 当页面被选中
         */
        @Override
        public void onPageSelected(int position) {
            // 显示体验按钮
            if (position == imageViewList.size() - 1) {
                btnStartExperience.setVisibility(View.VISIBLE);// 显示
            } else {
                btnStartExperience.setVisibility(View.GONE);// 隐藏
            }
        }
    
        /**
         * 当页面滚动状态改变
         */
        @Override
        public void onPageScrollStateChanged(int state) {
    
        }
    
        /**
         * 打开新的界面
         */
        @Override
        public void onClick(View v) {
            Toast.makeText(getApplicationContext(), "跳转新界面", Toast.LENGTH_SHORT).show();
        }
    
    
        class GuideAdapter extends PagerAdapter {
    
            @Override
            public int getCount() {
                return imageViewList.size();
            }
    
            @Override
            public boolean isViewFromObject(View view, Object object) {
                return view == object;
            }
    
            /*
             * 删除元素
             */
            @Override
            public void destroyItem(ViewGroup container, int position, Object object) {
                container.removeView((View) object);
            }
    
            @Override
            public Object instantiateItem(ViewGroup container, int position) {
                ImageView iv = imageViewList.get(position);
                container.addView(iv);// 1. 向ViewPager中添加一个view对象
                return iv; // 2. 返回当前添加的view对象
            }
        }
    }

    最近效果:

    到此,启动页已全部完成,告本篇一段落。源码下载:
    启动页(commonstartpage项目)源码GitHub下载地址

    ViewPager 从入门到带你撸个启动页之ViewPager基础入门(一)

    ViewPager 从入门到带你撸个启动页之Fragment+ViewPager(二)

    ViewPager 从入门到带你撸个启动页之实战启动页(三)

    ViewPager 从入门到带你撸个启动页之实战PageTransformer切换动画特效(四)

    展开全文
  • 需求描述:App里经常使用推送消息通知用户,如果不做任何操作,只给将App启动起来,这样没有任何效果,最佳实践是启动起来App后,自动拉起指定的页面,最好再有参数传递,通过参数来将数据加载到该页面上。...

    需求描述:

    App里经常使用推送消息通知用户,如果不做任何操作,只给将App启动起来,这样没有任何效果,最佳实践是启动起来App后,自动拉起指定的页面,最好再有参数传递,通过参数来将数据加载到该页面上。

    使用场景:

    1. App推送订单消息后,点击消息,启动App,跳转到订单详情页

    2. App推送聊天信息,点击消息后,启动App,跳转到聊天页面

    3. 分享出去的页面里带有链接,点击该链接后,启动App,跳转到目标页面。


    设计原理:

    1. App启动数据传递

    iOS里方法application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 里的launchOptions表示是的App启动时传递的数据。如果App后台已经死掉,推送消息过来后,会先拉起App,推送消息里的数据会通过launchOptions传递过来。

    2. 后台App接收推送后数据传递

    iOS AppDelegate里的application didReceiveRemoteNotification:(NSDictionary *)userInfo方法表示App在后台运行时,如果有推送,点击推送消息拉起App后通过userInfo传递数据。

    3. NSURL启动App传递数据

    AppDeletage中的application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation方法表示,当系统里打开某URL后,系统查询该URL是否有App注册,如果发现有App注册该URL的Scheme,则拉起该App,并将url传递过去。


    # 通过上述三个delegate可以实现前面的需求场景。



    实现:

    通过上述三种方法可以将推送数据或URL数据拿到,但是拿到后我们需要跳转到指定页面。
    假如我们将要做的跳转操作称为Action,我们定义Aciton的格式如下:
    action:
       wenxiaoyou://ACTION_NAME?param1=value1&param2=value2

    上述wenxiaoyou表示该操作的scheme用来区分其它的App注册的模式,一定要保证该scheme和别的App不一样,否则会拉错App。推荐使用App的包名。
    ACTION_NAME:表示你做的操作,一般我们打开App有很多操作,比如我们的App做留学一对一名校导师咨询的,有打开订单,导师首页,导师服务首页,导师评论页===
    param1=value1:表示要传递的参数,如果是订单,可以使用类似:wenxiaoyou://order_detail?order_id=1234,表示要打开订单详情页,该页面显示的是订单号为1234的内容。

    如果将App的scheme注册到系统中呢?
    打开Info.plist里添加scheme注册项:
    URL types
       > URL Schemes
          > Document Role : Editor
          > URL identifier : com.wenxiaoyou.com
          > URL Schemes:    wenxiaoyou

    通过上面的操作即可添加scheme项。


    // 实现App启动数据传递处理

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    ...

    #pragma mark- 处理启动时的推送通知

        NSDictionary* pushInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];

        if(pushInfo){//推送信息

            [self parsePushNotifaction:pushInfo];

        }

    ...

    }


    // 后台推送数据传递处理

    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo

    {

        NSLog(@"后台推送消息");

        [self parsePushNotifaction:userInfo];

    }


    // Web跳转处理一并处理

    - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation{

            NSString* action = [NSString stringWithFormat:@"%@://%@?%@", url.sheme, url.host, url.query];

            NSMutableDictionary* dic = [[NSMutableDictionary alloc]init];

            [dic setObject:action forKey:KEY_ACTION];

            // 保存起来要执行的任务

            SAVE_TO_USER_DEFAULT(dic, KEY_BOOT_TASK);

            [[NSNotificationCenter defaultCenterpostNotificationName:PUSH_MSG_BOOT_TASK_NOTIFICATION object:niluserInfo:dic];

    }


    // 推送消息处理

    -(void)parsePushNotifaction:(NSDictionary *)pushInfo

    {

        SAVE_TO_USER_DEFAULT(pushInfo, KEY_BOOT_TASK);

        [[NSNotificationCenter defaultCenterpostNotificationName:PUSH_MSG_BOOT_TASK_NOTIFICATION object:niluserInfo:pushInfo];


    }


    # SAVE_TO_USER_DEFAULT表示将推送内容及Web跳转信息保存到沙盒里。为什么要这么做???

    因为App收到推送时无非两种情况下:1. App未启动 2. App已经启动

    如果App未启动,当收到推送并点击推送信息时,会打开App,通过didFinishLaunchingWithOptions方法获得到推送内容。但是我们无法执行页面的跳转,因为在AppDelegate里还没有启动我们的“首页”,如果你直接打开目标页面也不是不可,但是目标页面如果点击返回,如何处理?所以,个人认为最好的方式就是先打开App的首页,然后再通过首页再push到目标页面,这样用户点击返回时,也会返回App首页。Ok,既然我们需要打开App首页,那我们推送里的跳转操作Action就需要暂时suspend起来,然后等App首页启动起来之后,再将suspend的Action启动起来。所以我们要将Action保存到沙盒里,等待首页启动起来之后,再执行suspend的Action。


    #为什么要使用通知Notification??

    因为parsePushNotifaction被调用了两次,一次是在App启动时,另外一次是在App已经启动时,当到Push消息后,我们可以直接实现跳转。为什么不直接在parsePushNotifaction里执行跳转呢??因为VC的启动与跳转都需要基于一个NaviController,而我们在AppDelegate里无法确定当前是哪个NaviController,虽然我们可以保存当前的NaviController。但是我还是希望使用下面这种方式,大家可以基于此展开讨论。


    我们的项目里所有VC都继承自BaseViewController,这个基类在init的时候,我们注册了PUSH_MSG_BOOT_TASK_NOTIFICATION通知的接收。

    这样,当前App里所有的页面都可以接收到要跳转Action的通知,但是,我们设定了,只有当前正在显示的VC才执行跳转,其它压栈的VC不执行跳转。

    伪代码如下:


    BaseViewController{

     init(){

        // 注册PUSH_MSG_BOOT_TASK_NOTIFICATION通知onBootActionNotify()

     }



     void onBootActionNotify(Notify* notifyInfo){

         // 如果当前VC不是最前端显示的VC,直接返回

         if(!self.isVisiable){

            return ;

         }

         

         // 如果Action已经被消费处理,则删除KEY_BOOT_TASK

         REMOVE_USER_DEFAULT(KEY_BOOT_TASK);


         // 如果当前App在后台运行,则将跳转操作放到Block里,等待App重新回到前台后自动执行该Block

         if(App.isInBackground){

           App.backBootBLock = {

               // 解析notifyInfo,并执行跳转

           };

         }else{

               // 解析notifyInfo,并执行跳转       

         }

     }

    }


    # 为了实现上述的后台重新唤醒后,自动执行跳转,需要在AppDelegate里添加Delegate

    -(void)applicationDidBecomeActive:(UIApplication *)application

    {

        APP.isInBackground = NO;

        if(App.backBootBlock){

           App.backBootBlock();

        }

    }


    -(void)applicationDidEnterBackground:(UIApplication *)application

    {

        APP.isInBackground = YES;

    }


    # OK,上述代码写完,好像没有问题了,仔细看下,好像首页启动后suspendAction的处理还没有添加。

    伪代码如下:

    HomeViewController{

      init(){

        // 如果因为推送或openURL启动了App,会有挂起的Action处理,这个时候可以处理它了。

        NSDictionary* notifyInfo = GET_OBJ_FROM_USER_DEFAULT(KEY_BOOT_TASK);

        if(notifyInfo != nil){

            [[NSNotificationCenter defaultCenterpostNotificationName:PUSH_MSG_BOOT_TASK_NOTIFICATION object:niluserInfo:notifyInfo];

        }

      }

    }



    以上



    展开全文
  • Android app启动页广告

    热门讨论 2016-12-15 10:26:22
    Android app启动也得广告,时间倒计时,支持跳过。详情见博客:http://blog.csdn.net/u010918416/article/details/52924930
  • Android 启动页面与广告页面的实现

    万次阅读 多人点赞 2017-03-26 16:46:27
    在我们APP的开发过程中,启动页面是绕不开的,广告页面说不定,但是不得不说,这两个界面都是经常要用到的。接下来我记录一下我的实现过程。项目架构为MVP。  那么先看看我们的需求和流程:(当然这也是可以根据...
  • react native 闪屏页(启动页)封装

    千次阅读 2017-06-05 17:41:37
    基本上大部分app都会有一个闪屏页(启动页),下面我们就封装一下,要求: 一张图片透明度在1500ms时间内由0.4变化为1。 提供图片source和动画执行完毕的回调。 /** * Created by on 2017/3/24. * 闪屏页,一般的...
  • * 首次打开时,启动页时间有10多秒过后再是引导页 * 非首次打开时,有5秒启动页的时间 * ![启动页](https://img-ask.csdn.net/upload/202001/23/1579712122_353012.png) ![图片说明]...
  • 启动页有一个停留3秒的广告,同时有一个线程在倒计时,现在问题是点击广告进入广告页后线程时间一到还是会打开主页,覆盖掉广告页,请问大佬们这个逻辑应该怎么写?
  • 微信小程序-设置启动页面

    万次阅读 2017-01-11 22:11:49
    在开发微信小程序的时候,除了tabBar上设置的页面之外,想进入肯定要走跳转之类的进入,但是我们开发的时候肯定想要直接看到该页面,那么我们就需要设置一下启动页面了有两种方法。方法一 在app.json文件中,pages...
  • WPF 启动页面

    千次阅读 2017-06-26 19:26:01
    如果我写的有误,请及时与我联系,我立即改之...wpf软件启动时,加载启动页面。软件初始化完成之后关闭页面。 App.xaml.cs代码 (实现加载页面功能) public partial class App : Application { protected ov
  • Android中启动页的实现

    千次阅读 2018-06-13 11:59:33
    我的启动页展示 背景 只要是一个app,每次打开的时候会显示启动页面,然后再进入主界面。下面看几个app的启动页展示: 我的思路 启动页面肯定是要打开就显示的,然后用一个Activity展示。界面...
  • 启动页变形

    2014-10-10 20:58:10
    http://www.penddy.com/about-android-boot-screen-resolution-adapter.html
  • react-native设置应用启动页

    万次阅读 2018-07-29 12:35:25
    在我们使用react-native进行编写代码的时候,当启动应用的时候,我们会看到如下界面 然而,这样的启动界面是非常的不又好,那么我们该怎么进行处理启动界面呢?有如下两种方案 二、方案 1.使用第三方库...
  • 从之前入门Android,一步一步的走到现在,过程真的难于...废话不多说,这次讲解APP的引导页启动页,我使用在代码中加入详细的注释的方式进行讲解,对于以下不经常出现的API我都进行了注解,接下来直接开始吧。 先...
  • Android 启动页适配&启动优化

    千次阅读 2019-01-23 20:30:33
    首当其冲的是就是启动页的适配了,尽管只有一张单图,为了保持他不变形绞尽脑汁想了无数办法,最终找到一个简单的方案。先说下当前的分辨率。 主流屏 16:9 1280*720 1.777 1920*1080 1.777 2560*1440 1.777 非主流...
  • Android启动页+引导页

    千次阅读 2017-04-29 19:15:54
    启动页代码如下: package com.example.sportsii.tixinwangapp.activity; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.os.Build; import ...
  • mui 启动页和引导页

    千次阅读 2017-09-30 14:02:06
    一、启动页和引导页的概念 “启动页”(splash):app的启动界面,每次启动都会看到的(每次打开QQ都会看到一只企鹅)。 “引导页”(guide):guide是否展示是可控的,通常首次启动时显示,从第二次启动开始,引导...
  • Android轻松实现高效的启动页

    千次阅读 2016-12-20 13:47:56
    说到Android的启动页,Google以前是把它作为反面教程来讲的,但是在材料设计规范里面,Google是支持开发者使用启动页的。这似乎好像有一点矛盾,但是新的建议和旧的建议都是站在一个角度来讲的,最好不要浪费用户的...
  • 问题描述:修改完launchScreen的启动图之后发现加载还是之前的图片? 问题解决思路:以为是缓存的问题,通过重启Xcode,删除掉APP重新安装,cleanXcode 发现 It don’t work; 那怎么办呢? 通过在网上搜索找到了...
  • APP启动页的正确配置方式

    千次阅读 2016-03-01 16:54:15
    在APP的启动页面(Splash Screen)好多都是等待3秒,好一点的还可以跳过,但是有的跳过也是假的按钮。当然像一些大厂的APP,像网易新闻等启动页面都是广告,人家要收广告费的。但是,对于一些普通的APP,有的也出现...
  • Android 启动页,倒计时 view

    千次阅读 2016-09-21 14:19:12
    Android 启动页,倒计时 view
  • Android启动页重复启动问题!

    千次阅读 2018-04-20 18:55:55
    当时用 Android 系统installer 安装应用后,点击打开应用,按home键切后台点击icon启动,会导致root activity重复启动。导致应用异常、卡顿等。 原因 Welcome to the ever-growing list of users who have been ...
  • Flutter 修改应用名称、图标、启动页

    千次阅读 2019-08-09 15:19:01
    虽然flutter可以同时运行在android和ios设备上,但是修改名称跟logo还是需要分开配置的。 修改应用名称 android 在项目下找到android目录,依次app》src》main》AndroidManifest.xml, 打开AndroidManifest.xml文件...

空空如也

1 2 3 4 5 ... 20
收藏数 33,438
精华内容 13,375
关键字:

启动页