Android仿小红书欢迎界面
1,觉得小红书的欢迎界面感觉很漂亮,就想来学习学习一下来实现类似于这种效果 。 原效果图如下:
2,根据效果我们来一点点分析
第一步:首先看一下我们的主界面布局文件视图效果如下:
main_activity.xml文件代码如下:
<?xml version="1.0" encoding="utf-8"?> <relativelayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@color/white" android:orientation="vertical" > <com.qianmo.xiaohongshuwelcome.parallaxpager.parallaxcontainer android:id="@+id/parallax_container" android:layout_width="match_parent" android:layout_height="match_parent"/> <imageview android:id="@+id/iv_man" android:layout_width="67dp" android:layout_height="202dp" android:layout_alignparentbottom="true" android:layout_centerhorizontal="true" android:layout_marginbottom="10dp" android:background="@drawable/intro_item_manrun_1" android:visibility="visible" /> </relativelayout>
可以看到我们主界面的布局文件主要是两个控件,一个是包含上面小人行走效果的imageview,然后是一个自定义parallaxcontainer控件,这个自定义控件的具体是什么我们先不要管,后面再和大家来慢慢解释
第二步:看一下我们主界面的mainactivity的代码
mainactivity.java
package com.qianmo.xiaohongshuwelcome; import android.app.activity; import android.content.activitynotfoundexception; import android.content.intent; import android.net.uri; import android.os.bundle; import android.view.view; import android.view.window; import android.view.windowmanager; import android.widget.imageview; import com.qianmo.xiaohongshuwelcome.parallaxpager.parallaxcontainer; /** * @author zhongdaxia 2014-12-15 */ public class mainactivity extends activity { imageview iv_man; imageview rl_weibo; parallaxcontainer parallaxcontainer; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); //获取当前窗体 final window window = getwindow(); window.setflags(windowmanager.layoutparams.flag_fullscreen, windowmanager.layoutparams.flag_fullscreen); setcontentview(r.layout.activity_main); /** * 动画支持11以上sdk,11以下默认不显示动画 * 若需要支持11以下动画,也可导入https://github.com/jakewharton/nineoldandroids */ if (android.os.build.version.sdk_int > 10) { iv_man = (imageview) findviewbyid(r.id.iv_man); parallaxcontainer = (parallaxcontainer) findviewbyid(r.id.parallax_container); if (parallaxcontainer != null) { parallaxcontainer.setimage(iv_man); parallaxcontainer.setlooping(false); iv_man.setvisibility(view.visible); parallaxcontainer.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_intro_6 ,r.layout.view_login); } } else{ setcontentview(r.layout.view_login); } } }
我们看到代码很简单,主要是这几句有用的代码:
if (parallaxcontainer != null) { parallaxcontainer.setimage(iv_man); parallaxcontainer.setlooping(false); iv_man.setvisibility(view.visible); parallaxcontainer.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_intro_6 ,r.layout.view_login); }
① 将我们小人走路的那个imageview添加到自定义控件parallaxcontainer中
② 将我们每一个的布局文件set到parallaxcontainer控件中去
这里给出r.layout.view_intro_1.xml文件代码,其他的类似,就不给出来了(这里的x_in、x_out、y_in等属性注意一下)
<?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" android:layout_width="fill_parent" android:layout_height="fill_parent" > <imageview android:id="@+id/iv_0" android:layout_width="103dp" android:layout_height="19dp" android:layout_centerinparent="true" android:src="@drawable/intro1_item_0" app:x_in="1.2" app:x_out="1.2" /> <imageview android:id="@+id/iv_1" android:layout_width="181dp" android:layout_height="84dp" android:layout_alignparentleft="true" android:layout_alignparenttop="true" android:layout_marginleft="13dp" android:layout_margintop="60dp" android:src="@drawable/intro1_item_1" app:x_in="0.8" app:x_out="0.8" /> <imageview android:id="@+id/iv_2" android:layout_width="143dp" android:layout_height="58dp" android:layout_alignparentright="true" android:layout_alignparenttop="true" android:layout_margintop="109dp" android:src="@drawable/intro1_item_2" app:x_in="1.1" app:x_out="1.1" /> <imageview android:id="@+id/iv_3" android:layout_width="48dp" android:layout_height="48dp" android:src="@drawable/intro1_item_3" app:x_in="0.8" app:x_out="0.8" app:a_in="0.8" app:a_out="0.8" android:layout_below="@+id/iv_0" android:layout_torightof="@+id/iv_5" android:layout_toendof="@+id/iv_5" android:layout_marginleft="21dp" android:layout_marginstart="21dp" android:layout_margintop="12dp"/> <imageview android:id="@+id/iv_4" android:layout_width="fill_parent" android:layout_height="128dp" android:layout_alignparentbottom="true" android:layout_marginbottom="29dp" android:background="@drawable/intro1_item_4" app:a_in="0.8" app:a_out="0.8" app:x_in="0.8" app:x_out="0.8" /> <imageview android:id="@+id/iv_5" android:layout_width="260dp" android:layout_height="18dp" android:layout_alignparentbottom="true" android:layout_alignparentleft="true" android:layout_marginbottom="16dp" android:layout_marginleft="15dp" android:src="@drawable/intro1_item_5" app:a_in="0.9" app:a_out="0.9" app:x_in="0.9" app:x_out="0.9" /> <imageview android:id="@+id/iv_6" android:layout_width="24dp" android:layout_height="116dp" android:layout_alignparentbottom="true" android:layout_alignparentleft="true" android:layout_marginbottom="35dp" android:layout_marginleft="46dp" android:src="@drawable/intro1_item_6" app:x_in="0.6" app:x_out="0.6" /> <imageview android:id="@+id/iv_7" android:layout_width="45dp" android:layout_height="40dp" android:layout_alignparentbottom="true" android:layout_alignparentleft="true" android:layout_marginbottom="23dp" android:layout_marginleft="76dp" android:src="@drawable/intro1_item_7" app:a_in="0.3" app:a_out="0.3" app:x_in="0.5" app:x_out="0.5" /> </relativelayout>
第三步:好了现在我们一定很好奇parallaxcontainer里面的内容,那我们从上面的方法慢慢去看,首先看一下parallaxcontainer中的setimage()方法,代码如下:
imageview iv; //将小人图片添加进来 public void setimage(imageview iv) { this.iv = iv; }
貌似没有什么,只是将它赋值给成员变量iv,我们接着看下一个setupchildren()方法
//添加子view 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()); //将所有的view添加到本控件上去 for (int childid : childids) { view view = parallaxlayoutinflater.inflate(childid, this); viewlist.add(view); } //添加视觉view pagecount = getchildcount(); for (int i = 0; i < pagecount; i++) { view view = getchildat(i); addparallaxview(view, i); } //更新viewpageradapter的数量 updateadaptercount(); //创建viewpager viewpager = new viewpager(getcontext()); viewpager.setlayoutparams(new layoutparams(layoutparams.match_parent, layoutparams.match_parent)); viewpager.setid(r.id.parallax_pager); //给viewpager添加滑动监听 attachonpagechangelistener(); //设置适配器 viewpager.setadapter(adapter); //将viewpager添加到主控件中 addview(viewpager, 0); }
让我们一行行代码慢慢分析
if (getchildcount() > 0) { throw new runtimeexception("setupchildren should only be called once when parallaxcontainer is empty"); }
//创建打气筒 parallaxlayoutinflater parallaxlayoutinflater = new parallaxlayoutinflater( inflater, getcontext());
首先看一下这段代码只是if判断是否已经调用过setupchildren()方法,没什么重要的,在看创建parallaxlayoutinflater打气筒对象,我们来看看parallaxlayoutinflater的具体代码,没什么重要的,只是里面有一个parallaxfactory类我们没见过,留心一下!
package com.qianmo.xiaohongshuwelcome.parallaxpager; import android.content.context; import android.view.layoutinflater; public class parallaxlayoutinflater extends layoutinflater { protected parallaxlayoutinflater(layoutinflater original, context newcontext) { super(original, newcontext); setuplayoutfactory(); } private void setuplayoutfactory() { if (!(getfactory() instanceof parallaxfactory)) { setfactory(new parallaxfactory(this, getfactory())); } } @override public layoutinflater cloneincontext(context newcontext) { return new parallaxlayoutinflater(this, newcontext); } }
再看下面一段代码,主要是将所有的布局文件添加到viewlist集合中去,并填充到我们的布局中,我们继续往下看
//将所有的view添加到本控件上去 for (int childid : childids) { view view = parallaxlayoutinflater.inflate(childid, this); viewlist.add(view); }
下面一段代码主要是看拿到对应所有的子view,关键是我们的addparallaxview()方法,具体代码如下:
//添加视觉view pagecount = getchildcount(); for (int i = 0; i < pagecount; i++) { view view = getchildat(i); addparallaxview(view, i); }
/** * 添加视觉view方法 * * @param view * @param pageindex */ private void addparallaxview(view view, int pageindex) { //通过递归方法拿到最小单元的view if (view instanceof viewgroup) { viewgroup viewgroup = (viewgroup) view; for (int i = 0, childcount = viewgroup.getchildcount(); i < childcount; i++) { addparallaxview(viewgroup.getchildat(i), pageindex); } } //创建视觉差view绑定,并添加到集合中去 parallaxviewtag tag = (parallaxviewtag) view.gettag(r.id.parallax_view_tag); if (tag != null) { tag.index = pageindex; parallaxviews.add(view); } }
通过递归将每个布局文件中的最小单元view保存到parallaxview集合中去,但是等等,这里我们又发现了一个新的类parallaxviewtag,让我们来具体代码
package com.qianmo.xiaohongshuwelcome.parallaxpager; public class parallaxviewtag { //绑定每一个view对应的是哪一个下标的 protected int index; //x轴进入的速度 protected float xin; protected float xout; protected float yin; protected float yout; protected float alphain; protected float alphaout; }
貌似很简单,xin、xout貌似很熟悉和我们之前的布局文件属性app:x_in等属性对应了, 这样我们就懂了,这个类是相当于一个tag类,用于记录我们设置的特殊一些属性,然是我们这里有一个疑问,下面这个代码是get到tag,那我们是在哪里set里面的属性呢?
parallaxviewtag tag = (parallaxviewtag) view.gettag(r.id.parallax_view_tag);
这时候我们要看看我们前面提到过的一个陌生类parallaxfactory,看一下具体代码
package com.qianmo.xiaohongshuwelcome.parallaxpager; import android.content.context; import android.content.res.typedarray; import android.util.attributeset; import android.view.layoutinflater; import android.view.view; import com.qianmo.xiaohongshuwelcome.r; public class parallaxfactory implements layoutinflater.factory { private final layoutinflater.factory factory; private parallaxlayoutinflater minflater; private static final string[] sclassprefixlist = { "android.widget.", "android.webkit.", "android.view." }; public parallaxfactory(parallaxlayoutinflater inflater, layoutinflater.factory factory) { minflater = inflater; this.factory = factory; } @override public view oncreateview(string name, context context, attributeset attrs) { view view = null; if (context instanceof layoutinflater.factory) { view = ((layoutinflater.factory) context).oncreateview(name, context, attrs); } if (factory != null && view == null) { view = factory.oncreateview(name, context, attrs); } if (view == null) { view = createvieworfailquietly(name, context, attrs); } if (view != null) { onviewcreated(view, context, attrs); } return view; } protected view createvieworfailquietly(string name, context context, attributeset attrs) { if (name.contains(".")) { return createvieworfailquietly(name, null, context, attrs); } for (final string prefix : sclassprefixlist) { final view view = createvieworfailquietly(name, prefix, context, attrs); if (view != null) { return view; } } return null; } protected view createvieworfailquietly(string name, string prefix, context context, attributeset attrs) { try { return minflater.createview(name, prefix, attrs); } catch (exception ignore) { return null; } } /** * 主要是在viewcreated的时候将tag和view绑定起来 * * @param view * @param context * @param attrs */ 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(); } } }
主要看onviewcreated()方法,可以看到,这里我们将对应的每个属性的值都set到了我们parallaxviewtag中,我们接着看下面的代码,调用updateadaptercount()方法让适配器去更新adapter的数量,这里我们可以看到适配器是继承pageradapter类,用于viewpager的适配器,这里使用linkedlist来存储view,这个方法很好,赞一下
//更新viewpageradapter的数量 updateadaptercount(); //具体代码 //被调用的时候好像是0 private void updateadaptercount() { adapter.setcount(islooping ? integer.max_value : pagecount); } //下面是adapter的具体代码 package com.qianmo.xiaohongshuwelcome.parallaxpager; import android.content.context; import android.support.v4.view.pageradapter; import android.view.view; import android.view.viewgroup; import java.util.linkedlist; import static android.view.viewgroup.layoutparams; import static android.view.viewgroup.layoutparams.match_parent; public class parallaxpageradapter extends pageradapter { private int count = 0; private final context context; private final linkedlist<view> recyclebin = new linkedlist<view>(); public parallaxpageradapter(context context) { this.context = context; } public void setcount(int count) { this.count = count; } @override public int getcount() { return count; } @override public object instantiateitem(viewgroup container, int position) { view view; if (!recyclebin.isempty()) { view = recyclebin.pop(); } else { view = new view(context); view.setlayoutparams(new layoutparams(match_parent, match_parent)); } container.addview(view); return view; } @override public void destroyitem(viewgroup container, int position, object object) { view view = (view) object; container.removeview(view); recyclebin.push(view); } @override public boolean isviewfromobject(view view, object object) { return view.equals(object); } }
我们继续往下看,后面的就是创建viewpager对象,并addview到主控件上,在attachonpagerchangelistener()方法中添加viewpager的滑动监听
/创建viewpager viewpager = new viewpager(getcontext()); viewpager.setlayoutparams(new layoutparams(layoutparams.match_parent, layoutparams.match_parent)); viewpager.setid(r.id.parallax_pager); //给viewpager添加滑动监听 attachonpagechangelistener(); //设置适配器 viewpager.setadapter(adapter); //将viewpager添加到主控件中 addview(viewpager, 0);
这里我们在onpagescrollstatechanged()方法判断是否开启下面小人行走的动画,通过onpagescrolled()方法监听滑动的具体偏移量,通过view.settranslationx()方法来改变对应的属性
protected void attachonpagechangelistener() { mcommonpagechangelistener = new viewpager.onpagechangelistener() { /** * 此方法是在状态改变的时候调用,其中arg0这个参数 有三种状态(0,1,2)。arg0 ==1的时辰默示正在滑动,arg0==2的时辰默示滑动完毕了,arg0==0的时辰默示什么都没做。 * @param state */ @override public void onpagescrollstatechanged(int state) { log.v(tag, "onpagescrollstatechanged" + state); iv.setbackgroundresource(r.drawable.man_run); final animationdrawable animationdrawable = (animationdrawable) iv.getbackground(); switch (state) { case 0: //处于展示阶段 finishanim(animationdrawable); break; case 1: //正在滑动 isend = false; animationdrawable.start(); break; case 2: //滑动完毕 finishanim(animationdrawable); break; } } //判断是否还是在左边 boolean isleft = false; /** * onpagescrolled(int arg0,float arg1,int arg2) ,当页面在滑动的时候会调用此方法,在滑动被停止之前,此方法回一直得到调用。其中三个参数的含义分别为: * @param pageindex 当前页面,及你点击滑动的页面 * @param offset 当前页面偏移的百分比 * @param offsetpixels 当前页面偏移的像素位置 */ @override public void onpagescrolled(int pageindex, float offset, int offsetpixels) { // log.v(tag, "onpagescrolled" + pageindex + " offset" + offset + " offsetpixels" + offsetpixels); if (offsetpixels < 10) { isleft = false; } if (pagecount > 0) { pageindex = pageindex % pagecount; } if (pageindex == 3) { if (isleft) { } else { iv.setx(iv.getleft() - offsetpixels); } } parallaxviewtag tag; 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); } } } @override public void onpageselected(int position) { log.v(tag, "onpageselected" + position); currentposition = position; } }; viewpager.setonpagechangelistener(mcommonpagechangelistener); }
4,这里基本上就把源码分析完了,so,既然分析完别人的源码了下面就是结合到自己项目中去用了,当我们,想要实现一个翻页从顶部斜飞入的view,那我们的布局文件代码可以如下:
<?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" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@color/transparent"> <imageview android:id="@+id/iv_2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignparenttop="true" android:layout_centerhorizontal="true" android:layout_marginleft="133dp" android:layout_margintop="39dp" android:src="@drawable/ic_launcher" app:x_in="1" app:x_out="1" app:y_in="0.6" app:y_out="0.9"/> <imageview android:id="@+id/iv_11" android:layout_width="44dp" android:layout_height="47dp" android:layout_alignparentbottom="true" android:layout_centerhorizontal="true" android:layout_marginbottom="66dp" android:layout_marginleft="140dp" android:src="@drawable/ic_launcher" app:x_in="1" app:x_out="1" app:y_in="-1.3" app:y_out="-1.3"/> </relativelayout>
来看一下我们的效果:
这是github下载地址,由于要源码的同学可以去下载一下,see you next time !!!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!