Android引导页的魅力

Android引导页的魅力

       几乎所有的APP都有引导页,可见引导页的魅力有多大,引导页能迅速抓住用户的眼球,让用户很快的了解该app的主张。一个好的引导页能提升用户体验,增强用户好感度,甚至有些应用优美的引导页让人有卸载重装的冲动,就为了再看一遍引导页。那么,app的引导页都用什么做的呢?大致可以分为3类:

1、普通的viewpager引导页

这是最普遍的一种做法,很多app就是使用这种方式实现,刚开始流行的时候可能会觉得比较新奇,但是,时间久了,人们都形成了审美疲劳了。

2、视差引导页

这种比第一种方式强很多,但是用得不多,为什么呢?因为第三种。这种方式开起来很炫酷,很牛B,但是越炫酷越难实现(相比于html5来说)。

3、HTML5引导页

现在越来越多的app开始使用这种方式,HTML5在动画方面很强大,所以能够弄出来很炫酷,很牛逼的效果。

 

普通的Viewpager引导页

先上效果图:

 

请忽略图片中的指示器,那是之前的项目设计为了省事直接将指示器放在了图片上面,请不要过分关注。

 

1.    在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:orientation="vertical"
>

    <android.support.v4.view.ViewPager
       
android:id="@+id/normal_view_pager"
       
android:layout_width="match_parent"
       
android:layout_height="match_parent"
></android.support.v4.view.ViewPager>
    <Button
       
android:layout_width="@dimen/act_wel_btn_width"
       
android:layout_height="wrap_content"
       
android:textColor="@android:color/white"
       
android:textSize="@dimen/act_wel_btn_textSize"
       
android:layout_centerHorizontal="true"
       
android:id="@+id/act_welcom_btn_start"
       
android:layout_marginBottom="@dimen/act_wel_btn_marginBottom"
       
android:layout_alignParentBottom="true"
       
android:background="@null"
       
android:visibility="gone"
       
/>
</RelativeLayout>

 

2.    编写Adapter类

public class NormalPagerAdapter extends PagerAdapter {
    private Context context;
    private List<ImageView> mDatas;

    public NormalPagerAdapter(Context context, List<ImageView> mDatas) {
        this.context = context;
        this.mDatas = mDatas;
    }

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

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

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

    @Override
    public ImageView instantiateItem(ViewGroup container, int position) {
        container.addView(mDatas.get(position));
        return mDatas.get(position);
    }
}

 

3.    初始化Viewpager,初始化视图数据、添加监听器,等等

public class NormalViewPager extends Activity {
    private ViewPager viewPager;
    private List<ImageView> mDatas;
    private NormalPagerAdapter adapter;
    private Button startBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_normal_view_pager);
        viewPager = (ViewPager) findViewById(R.id.normal_view_pager);
        startBtn = (Button) findViewById(R.id.act_welcom_btn_start);
        startBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                NormalViewPager.this.finish();
            }
        });
        initData();
        initAdapter();
    }

    private void initData() {
        ImageView view1 = new ImageView(this, null);
        view1.setBackgroundResource(R.drawable.pic_wel_a);
        ImageView view2 = new ImageView(this);
        view2.setBackgroundResource(R.drawable.pic_wel_b);
        ImageView view3 = new ImageView(this);
        view3.setBackgroundResource(R.drawable.pic_wel_c);
        ImageView view4 = new ImageView(this);
        view4.setBackgroundResource(R.drawable.pic_wel_d);
        ImageView view5 = new ImageView(this);
        view5.setBackgroundResource(R.drawable.pic_wel_e);
        mDatas = new ArrayList<>();
        mDatas.add(view1);
        mDatas.add(view2);
        mDatas.add(view3);
        mDatas.add(view4);
        mDatas.add(view5);
    }

    private void initAdapter() {
        adapter = new NormalPagerAdapter(this, mDatas);
        viewPager.setAdapter(adapter);
        viewPager.setPageTransformer(true, new DepthPageTransformer());
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {
                switch (position) {
                    case 0:
                        startBtn.setVisibility(View.GONE);
                        break;
                    case 1:
                        startBtn.setVisibility(View.GONE);
                        break;
                    case 2:
                        startBtn.setVisibility(View.GONE);
                        break;
                    case 3:
                        startBtn.setVisibility(View.GONE);
                        break;
                    case 4:
                        startBtn.setVisibility(View.VISIBLE);
                        break;
                }
            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
    }

}

 

上面的代码添加了监听器,同时设置了按钮的显示事件,viewpager是最简单也是目前比较普遍的引导页实现方式,就不做过多的讲解。

 

视差引导页

       先上两张两张效果图,一张是ecmobile稍微改了一下,另一张是小红书

 

 

效果是不是很不错!那么简单说一下实现原理,原理就一句话:层级不同,滑动速度不同。

 

Ecmobile的实现

1.    xml文件

由于xml代码有点儿多,就不贴出来了,这里只是给出布局层级图


层级为3层

·        背景图层 back_image_one

·        layer层 内含FrameLayout,一个FrameLayout对应一个ViewPager的pager页面。我们要控制的就是FrameLayout中的元素的滑动速度

·        ViewPager层

2.    Adapter的实现

代码较多,我只贴instantiateItem函数

@Override
public Object instantiateItem(ViewGroup container, int position) {
    final ViewHolde holder;
    holder = new ViewHolde();
    View imageLayout = mInflater.inflate(R.layout.gallery_image_item, null);
    holder.image = (LinearLayout) imageLayout.findViewById(R.id.gallery_image_item_view);
    if (position == 4) {
        holder.image.setEnabled(true);
    } else {
        holder.image.setEnabled(false);
    }
    if (position == 0) {
        holder.image.removeAllViews();
        View view0 = inflater.inflate(R.layout.lead_a, null);
        holder.image.addView(view0);
    } else if (position == 1) {
        holder.image.removeAllViews();
        View view1 = inflater.inflate(R.layout.lead_b, null);
        holder.image.addView(view1);
    } else if (position == 2) {
        holder.image.removeAllViews();
        View view2 = inflater.inflate(R.layout.lead_c, null);
        holder.image.addView(view2);
    } else if (position == 3) {
        holder.image.removeAllViews();
        View view3 = inflater.inflate(R.layout.lead_d, null);
        holder.image.addView(view3);
    } else if (position == 4) {
        holder.image.removeAllViews();
        View view4 = inflater.inflate(R.layout.lead_e, null);
        holder.image.addView(view4);
    }
    ((ViewPager) container).addView(imageLayout, 0);
    return imageLayout;
}
 
看看gallery_image_item.xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#00000000"
    android:orientation="vertical" >

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

        <LinearLayout
            android:id="@+id/gallery_image_item_view"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >
        </LinearLayout>
    </FrameLayout>

</LinearLayout>

结合代码我们知道,逻辑就是根据position,向这个布局中添加对应的视图(添加之前先要移除原来添加的视图)。这里要注意,这个布局是透明的。

看看lead_a的效果


到这里,视图就搞清楚了,那么我们来看看代码是如何控制速度的。

3.    设置和控制

我们在层级数中知道,第二层是一个横向滑动的FrameLayout。所以我们需要设置每个FrameLayout的大小

 
FrameLayout.LayoutParams framLayoutParams;
ImageView layer_image_one = (ImageView) findViewById(R.id.layer_image_one);
framLayoutParams = (FrameLayout.LayoutParams) layer_image_one.getLayoutParams();
framLayoutParams.height = dm.heightPixels;
framLayoutParams.width = dm.widthPixels;
layer_image_one.setLayoutParams(framLayoutParams);

 

其它几个图层实现方式一样。

当然,我们也需要设置一下最下层的背景。

ImageView back_image_one = (ImageView) findViewById(R.id.back_image_one);
layoutParams = back_image_one.getLayoutParams();
layoutParams.height = dm.heightPixels;
layoutParams.width = dm.widthPixels;
back_image_one.setLayoutParams(layoutParams);

 

这个地方设置一个就OK啦,因为在这里我们的背景没有滑动过,当然是不是需要重新设置要看具体的需求是什么。

最关键的一点,给viewPager设置监听器,并在onPageScrolled中监听滑动并设置layer层元素的滑动,同时在onPageSelected中监听按钮的隐藏和显现。

viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        float realOffset = Cubic.easeIn(positionOffset, 0, 1, 1);
        total_page = adapter.getCount();
        float offset = (float) ((float) (position + realOffset) * 1.0 / total_page);
        int offsetPosition = (int) (backgroundWidth * offset);
        float layerRealOffset = Sine.easeIn(positionOffset, 0, 1, 1);
        float layerOffset = (float) ((float) (position + layerRealOffset) * 1.0 / total_page);
        int layerOffsetPosition = (int) (backgroundWidth * layerOffset);
        layer_srcollview.scrollTo(layerOffsetPosition, 0);
    }

    @Override
    public void onPageSelected(int position) {
        switch (position) {
            case 0:
                startBtn.setVisibility(View.GONE);
                break;
            case 1:
                startBtn.setVisibility(View.GONE);
                break;
            case 2:
                startBtn.setVisibility(View.GONE);
                break;
            case 3:
                startBtn.setVisibility(View.GONE);
                break;
            case 4:
                startBtn.setVisibility(View.VISIBLE);
                break;
        }
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }
});

 

看看easnIn的实现

public static float easeIn(float t, float b, float c, float d) {
    return -c * (float) Math.cos(t / d * (Math.PI / 2)) + c + b;
}

上面这个方法就是余弦波的3PI/2到2PI的波形状,

float layerOffset = (float) ((float) (position + layerRealOffset) * 1.0 / total_page);

再加上position([0,1]),layerOffset也就到了0到1的范围,波的形状是倒过来的cos波,开始的1/4段,这样波斜率(对应速度)也就越来越快,知道停止,这样就形成了我们上图看到的视差。

 

小红书的实现

先贴出目录结构


其中最关键的是ParallaxContainer(自定义布局)和ParallaxLayoutInflater(加载布局)


几个布局页面里面都是些ImageView,我们可以发现ImageView里面有了新的属性,(这里报红没有关系,可以正常编译运行的),一定不要忘记在根布局中添加

xmlns:app="http://schemas.android.com/apk/res-auto"

看看属性类:

public class ParallaxViewTag {
    protected int index;
    protected float xIn;
    protected float xOut;
    protected float yIn;
    protected float yOut;
    protected float alphaIn;
    protected float alphaOut;
}

再看看attrs.xml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="a_in" format="float" />
    <attr name="a_out" format="float" />
    <attr name="x_in" format="float" />
    <attr name="x_out" format="float" />
    <attr name="y_in" format="float" />
    <attr name="y_out" format="float" />
</resources>

接下来看看小红书是怎么实现的:

if (mParallaxContainer != null) {
    mParallaxContainer.setImage(iv_man);
    mParallaxContainer.setLooping(false);

    iv_man.setVisibility(View.VISIBLE);
    mParallaxContainer.setupChildren(getLayoutInflater(),
            R.layout.view_intro_1, R.layout.view_intro_2,
            R.layout.view_intro_3, R.layout.view_intro_4,
            R.layout.view_intro_5, R.layout.view_login);
}

 

设置行走的人,设置子布局。我们来看看setupChildren方法:

public void setupChildren(LayoutInflater inflater, int... childIds) {
    if (getChildCount() > 0) {
        throw new RuntimeException("setupChildren should only be called once when ParallaxContainer is empty");
    }

    ParallaxLayoutInflater parallaxLayoutInflater = new ParallaxLayoutInflater(
            inflater, getContext());

    for (int childId : childIds) {
        View view = parallaxLayoutInflater.inflate(childId, this);
        viewlist.add(view);
    }

    pageCount = getChildCount();
    for (int i = 0; i < pageCount; i++) {
        View view = getChildAt(i);
        addParallaxView(view, i);
    }

    updateAdapterCount();

    viewPager = new ViewPager(getContext());
    viewPager.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));
    viewPager.setId(R.id.parallax_pager);
    attachOnPageChangeListener();
    viewPager.setAdapter(adapter);
    addView(viewPager, 0);
}

 

·        将布局页加载进来

·         for (int childId : childIds) {
    View view = parallaxLayoutInflater.inflate(childId, this);
    viewlist.add(view);
}

pageCount = getChildCount();
for (int i = 0; i < pageCount; i++) {
    View view = getChildAt(i);
    addParallaxView(view, i);
}

 

·        初始化ViewPager并设置监听

 

viewPager = new ViewPager(getContext());
viewPager.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));
viewPager.setId(R.id.parallax_pager);
attachOnPageChangeListener();
viewPager.setAdapter(adapter);
addView(viewPager, 0);

 

我们再来看看addParallaxView的方法:

private void addParallaxView(View view, int pageIndex) {
    if (view instanceof ViewGroup) {
        ViewGroup viewGroup = (ViewGroup) view;
        for (int i = 0, childCount = viewGroup.getChildCount(); i < childCount; i++) {
            addParallaxView(viewGroup.getChildAt(i), pageIndex);
        }
    }

    ParallaxViewTag tag = (ParallaxViewTag) view.getTag(R.id.parallax_view_tag);
    if (tag != null) {
        tag.index = pageIndex;
        parallaxViews.add(view);
    }
}

这个方法就是用来添加子view的。

attachOnPageChangeListener方法源码有些长,我们就只看看关键的代码。就是初始化ViewPager.OnPageChangeListener监听器,并在onPageScrolled方法中根据我们的tag和偏移量来移动view。

 

for (View view : parallaxViews) {
    tag = (ParallaxViewTag) view.getTag(R.id.parallax_view_tag);
    if (tag == null) {
        continue;
    }

    if ((pageIndex == tag.index - 1 || (isLooping && (pageIndex == tag.index
            - 1 + pageCount)))
            && containerWidth != 0) {

        // make visible
        view.setVisibility(VISIBLE);

        // slide in from right
        view.setTranslationX((containerWidth - offsetPixels) * tag.xIn);

        // slide in from top
        view.setTranslationY(0 - (containerWidth - offsetPixels) * tag.yIn);

        // fade in
        view.setAlpha(1.0f - (containerWidth - offsetPixels) * tag.alphaIn / containerWidth);

    } else if (pageIndex == tag.index) {

        // make visible
        view.setVisibility(VISIBLE);

        // slide out to left
        view.setTranslationX(0 - offsetPixels * tag.xOut);

        // slide out to top
        view.setTranslationY(0 - offsetPixels * tag.yOut);

        // fade out
        view.setAlpha(1.0f - offsetPixels * tag.alphaOut / containerWidth);

    } else {
        view.setVisibility(GONE);
    }
}

这里频繁出现的Tag是什么时候设置的呢?仔细想想,应该是在加载的时候进行设置的,那我们看看setupChildren方法中有没有想过设置。发现如下代码:

ParallaxLayoutInflater parallaxLayoutInflater = new ParallaxLayoutInflater(
        inflater, getContext());

for (int childId : childIds) {
    View view = parallaxLayoutInflater.inflate(childId, this);
    viewlist.add(view);
}

 

每个子view都是通过ParallaxLayoutInflater来加载的。那么我们看看ParallaxLayoutInflater是怎么做的,代码如下:

public class ParallaxLayoutInflater extends LayoutInflater {
    protected ParallaxLayoutInflater(LayoutInflater original, Context context){
        super(original, context);
        setUpLayoutFactory();
    }

    private void setUpLayoutFactory(){
        if(!(getFactory() instanceof  ParallaxFactory)){
            setFactory(new ParallaxFactory(this,getFactory()));
        }
    }

    @Override
    public LayoutInflater cloneInContext(Context newContext) {
        return null;
    }
}

 

我们可以发现构造方法中,通过setFactory方法,设置Factory为ParallaxFactory,到这里,就明白了。ParallaxFactory实现了LayoutInflater.Factory,这个接口有什么用。我们看看该接口的介绍:

public interface Factory {
    /**
     * Hook you can supply that is called when inflating from a LayoutInflater.
     * You can use this to customize the tag names available in your XML
     * layout files.
     * 
     * <p>
     * Note that it is good practice to prefix these custom names with your
     * package (i.e., com.coolcompany.apps) to avoid conflicts with system
     * names.
     * 
     * @param name Tag name to be inflated.
     * @param context The context the view is being created in.
     * @param attrs Inflation attributes as specified in XML file.
     * 
     * @return View Newly created view. Return null for the default
     *         behavior.
     */
    public View onCreateView(String name, Context context, AttributeSet attrs);
}

大概是说,我们可以通过实现这个接口来获取xml文件中的tag(就像app:x_in等等属性),由于ImageView不是我们的自定义控件,我们没法再ImageView的构造方法中获取一些值,所以通过实现这个接口来获取。

protected void onViewCreated(View view, Context context, AttributeSet attrs) {

    int[] attrIds =
            { R.attr.a_in, R.attr.a_out, R.attr.x_in, R.attr.x_out, R.attr.y_in, R.attr.y_out,  };

    TypedArray a = context.obtainStyledAttributes(attrs, attrIds);

    if (a != null) {
        if (a.length() > 0) {
            ParallaxViewTag tag = new ParallaxViewTag();
            tag.alphaIn = a.getFloat(0, 0f);
            tag.alphaOut = a.getFloat(1, 0f);
            tag.xIn = a.getFloat(2, 0f);
            tag.xOut = a.getFloat(3, 0f);
            tag.yIn = a.getFloat(4, 0f);
            tag.yOut = a.getFloat(5, 0f);
            view.setTag(R.id.parallax_view_tag, tag);
        }
        a.recycle();
    }
}

这上面的方法中,我们获取了属性并给view设置了tag,所以,在后面我们就可以通过tag来获取这些值了。

接下来看看Adapter。

@Override
public Object instantiateItem(ViewGroup container, int position) {

    View view;
    if (!recycleBin.isEmpty()) {
        view = recycleBin.pop();
    } else {
        view = new View(context);
        //这里注意,如果不想加前缀,请以静态的方式将包和属性导进来
        view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    }
    container.addView(view);
    return view;
}

什么都没有的空白页,我们的所有逻辑都在ParallaxFactory这个自定义Layout里面,图片什么的也都是,所以我们不需要ViewPager有什么东西(这里是跟ecmobiled的区别之一),我们的viewpager只负责滑动,只负责触发onPageScrolled方法,剩下的就是有view的setTranslationX、setTranslationY、setAlpha方法来实现。

 

HTML5实现引导页

使用HTML5制作引导页,对前端开发能力要求很高,当然我说的是实现炫酷效果的前提下,如果前端知识不足做出来的效果可能还赶不上普通引导页的效果,我承认我的前端开发能力就很差,所以我做出来的效果就比较丑,没有什么炫酷的动画在里面,当然只是借此效果来讲解HTML5实现引导页的方式,如果你的前端知识是在匮乏,你可以找公司的前端,让他帮助你实现一个狂炫酷拽吊炸天的效果,duang~~,好了不多说,先看看效果图:

 

声明:真正的HTML5实现的效果比图片上的效果好上很多倍,我只是简单的展示一下效果,吐槽什么的尽管释放,不要压抑心中的想法。

 

看看我的assets文件结构


首先我们分析一下,都需要哪些工作?

1、制作HTML5引导页。

2、把做好的页面放入Android工程中的assets文件夹下。

3、利用WebView加载assets文件夹下的html文件。

4、在引导页最后一页的按钮上捕捉点击事件,结束引导页,进入应用。

 

具体实现,首先看看xml布局文件

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

    <WebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none"></WebView>
</LinearLayout>
 

再来看看index.html的UI部分:

<head>
    <meta charset="utf-8">
    <title>jQuery加CSS3 实现slide动画</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
    <meta name="apple-mobile-web-app-capable" content="yes"/>
    <link href="style.css" type="text/css" rel="stylesheet"/>
    <script src="js/jquery-2.1.4.min.js"></script>
</head>
<body>
<div id="position-nav"></div>
<div id="slides">
    <div class="slide-list"></div>
    <div class="slide-list"></div>
    <div class="slide-list"></div>
    <div class="slide-list">
        <button οnclick="window.open('http:start')">立即体验</button>
    </div>
</div>
</body>

我们可以发现在最后一个div中有一个按钮,按钮中添加了点击事件,我们就是通过webview和原生通过js交互捕捉按钮点击事件,具体的CSS和JS我就不讲了,看看文件中的源码,因为我也是让我们的前端大哥帮忙做的,当然前端大哥比较忙,我也不好意思太麻烦人家,就让他帮忙简单做了一下效果。

最后看看Activity中的实现:

public class Html5ForWelActivity extends Activity {
    private WebView webView;
    private String url;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setContentView(R.layout.activity_second);
        webView = (WebView) findViewById(R.id.webview);
        url = "file:///android_asset/index.html";
        loadLocalHtml(url);
    }

    @SuppressLint({"JavascriptInterface", "SetJavaScriptEnabled"})
    private void loadLocalHtml(String url) {
        WebSettings webSettings = webView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                if ("http://start/".equals(url)) {
//                    Intent intent = new Intent(Html5ForWelActivity.this, MainActivity.class);
//                    startActivity(intent);
                    finish();
                }
                return super.shouldOverrideUrlLoading(view, url);
            }

            @Override
            public void onLoadResource(WebView view, String url) {
                super.onLoadResource(view, url);
            }
        });
        webView.loadUrl(url);
    }
}

注意:不要忘了loadLocalHtml(String url)上面的注释,如果没有该注释js交互无效。

       另外,当使用WebView浏览网页时,不做处理的话,按下手机的返回键会直接结束WebView所在的Activity,通过重写onKeyDown()方法,当WebView可以返回时,让其执行返回操作。

 

总结

       通过上面的讲解,可以看出:

普通引导页,实现简单也是最常使用的一种方式,但是缺乏很多炫酷的效果,比较机械。

视差引导页,效果明显比普通引导页炫酷了很多,从技术实现角度来讲,对开发能力要求较高,而且视差引导页出在一个不上不下的地位,很容易被Html5取代。

HTML5引导页,HTML5对动画的完美支持很容易实现炫酷的引导效果,越是炫酷的效果就越是需要更高的前端开发能力。

 

感谢全世界-gl提供的普通引导页和视差引导页的讲解,参考:http://blog.csdn.net/qq_21430549/article/details/50066295

 

资源下载地址:http://download.csdn.net/detail/zhimingshangyan/9470174

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值