高逼格动画---自定义弹出式窗口+优化
2017-01-06 15:08
399 查看
今天给大家带来的给最底层的布局添加控件的原理分析。首先看下效果吧↓
(我是给第一个Ima'ge)看完之后估计有些人认为很简单。具体你的看法是怎么样的我就不过多的猜测了,开始分析一下这个效果吧!首先大家看到了上面会有一块会弹上去,弹到顶之后又会恢复成直的。这种效果怎么实现呢,咱先不说 先说一下调用这个需要几行代码看我截图
这也是这篇博客的重点之一(●ˇ∀ˇ●) 。
重点2,这个自定义控件不是加在这个带有ScrollView的这个布局文件中的,是添加在了最顶层的布局文件。好了,开始分析了!!!
R.layout.activity_main:
看主Activity的布局,啥也没有就一个ScrollView 为了能撑起这个ScrollView 就在里面放了几个ImageView。其它的啥也没有,这也就证实了刚才说的,不是加在这里的,那是加在什么地方呢。要开始重点了,我要带大家进源码了。好,我开始说了不在我们自己的布局里面添加,要在最顶层的布局里面添加,(注:我们的布局写好之后都会添加到系统的一个布局里面的),所以我们就要去着个布局。我们的入口就是在setContentView这里,因为只有这个方法跟我们的布局发生了关系。好的,我们点进去看一看
可以看到,一个getWindow,原来是Window来渲染我们的布局的啊。那就再去看看Window这个类呗,进去一看是个abstract的,这就有点那里疼了。。。。。找了半天找了个抽象类出来,白找了。NO。。没有事,到了Window类之后看看上面的注释
这个就说明了他是有个实现类的叫PhoneWindow的,那么我们去着PhoneWindow。(Elicpse的估计需要下载源码,Studio的可以按两下shift键之后把PhoneWindow复制上去就好了。)
进来之后是不是已经忘记自己要干什么了啊?我们是来着setContentView的,那就搜索一下。看到了这个setContentView方法之后,我就不跟大家互动了啊,直接捞干货说了啊。找到setContentView之后看到都是调用的这个有两个参数的setContentView(View view, ViewGroup.LayoutParams params)方法,那就没错了,就是他了。我们往下看,其它的不看就看重点,第一行代码,就是判断父容器是不是为空,为空的话就调用installDecor()方法。也就是说这个方法会返回顶级父容器,那么就点进去看看。
进去一看,前两行代码就为了实例化这个mDecor那这个mDecor就是个重点了。看看它是什么,上去一看是一个DecorView。看到这里可能有一部分人知道怎么回事了。没错,它就是PhoneWindow的第一层布局,我们自己布局的最顶层的父容器,它其实是个帧布局(可以进去看一下它是继承FragmeLayout的)。
找到这里还没有完事,还得找,找什么?找它还干了什么!
大家看如果DecorView不等于null会用这个mDecor返回一个mContentParent,那我们肯定要看看这个generateLayout方法干什么了呀。进去一看,妈呀,这个方法这么长,但是不要紧,看重点,先不管它这些判断,找我们的DecorView。
在这里!我们的DecorView在这里加了一个布局。那么要看看这个叫叫in的View什么样子啊。我就不上图了,直接口述了。这个in什么样,取决于layoutResource
所以就看这个LayoutResource什么样,这个layoutResource是在判断里面赋值的。我就不跟大家绕了,其实它的样子取决于你设置的theme,我们随便进去一个看一下↓
我相信大家看到这个图应该就恍然大悟了。
其实通过刚才的分析,android系统的布局应该是这样的。
本图是我自己画的,不对的地方希望大家指正。
其实就是在我们写布局的时候系统就把带有ActionBar的给我们了,然后我们的布局其实都是放在id为content的FragmeLayout的布局里面的。
好了 说到这里好像跑题了,但是其实是没跑题,我好像写的文章都带在跟大家一起来分享我对源码的认识,很多东西还得从源码看起啊。
好,下面开始撸起来!!!开始我们就说了要在最顶层的布局上添加我们的特效,那么我们首先要找到最顶层的布局,所以我写了一个查找到顶层容器的工具类。请看我的代码↓
这个类只是一个查找父容器和一些操作的工具类。要有上面哪个回弹的效果还得看我们的自定义控件。如果大家有一定的经验的话,一定就看得出自定义组件的头部分是用的贝塞尔曲线做的。如果不知道的也可以给我评论下,我再单独说一下也可以,在这里就不多说了,直接上代码了。
总结一下:这篇博客的重点就是两个
1、怎么封装一个让别人用的很爽的工具类(Builder模式)
2、了解Android的布局文件结构
源码我总是会放到最后,因为我认为既然是来找东西的,肯定就是有欠缺的地方,所以我想让大家好好的看完文章。(其实我是想让大家跟我一起探讨学习,嘿嘿)
希望大家有时间多跟我交流
(我是给第一个Ima'ge)看完之后估计有些人认为很简单。具体你的看法是怎么样的我就不过多的猜测了,开始分析一下这个效果吧!首先大家看到了上面会有一块会弹上去,弹到顶之后又会恢复成直的。这种效果怎么实现呢,咱先不说 先说一下调用这个需要几行代码看我截图
这也是这篇博客的重点之一(●ˇ∀ˇ●) 。
重点2,这个自定义控件不是加在这个带有ScrollView的这个布局文件中的,是添加在了最顶层的布局文件。好了,开始分析了!!!
R.layout.activity_main:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rl" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#f2f2f2"> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <ImageView android:id="@+id/show" android:layout_width="match_parent" android:layout_height="300dp" android:src="@mipmap/ic_launcher" /> <ImageView android:layout_width="match_parent" android:layout_height="300dp" android:src="@mipmap/ic_launcher" /> <ImageView android:layout_width="match_parent" android:layout_height="300dp" android:src="@mipmap/ic_launcher" /> <ImageView android:layout_width="match_parent" android:layout_height="300dp" android:src="@mipmap/ic_launcher" /> <ImageView android:layout_width="match_parent" android:layout_height="300dp" android:src="@mipmap/ic_launcher" /> </LinearLayout> </ScrollView> </RelativeLayout>
看主Activity的布局,啥也没有就一个ScrollView 为了能撑起这个ScrollView 就在里面放了几个ImageView。其它的啥也没有,这也就证实了刚才说的,不是加在这里的,那是加在什么地方呢。要开始重点了,我要带大家进源码了。好,我开始说了不在我们自己的布局里面添加,要在最顶层的布局里面添加,(注:我们的布局写好之后都会添加到系统的一个布局里面的),所以我们就要去着个布局。我们的入口就是在setContentView这里,因为只有这个方法跟我们的布局发生了关系。好的,我们点进去看一看
可以看到,一个getWindow,原来是Window来渲染我们的布局的啊。那就再去看看Window这个类呗,进去一看是个abstract的,这就有点那里疼了。。。。。找了半天找了个抽象类出来,白找了。NO。。没有事,到了Window类之后看看上面的注释
这个就说明了他是有个实现类的叫PhoneWindow的,那么我们去着PhoneWindow。(Elicpse的估计需要下载源码,Studio的可以按两下shift键之后把PhoneWindow复制上去就好了。)
进来之后是不是已经忘记自己要干什么了啊?我们是来着setContentView的,那就搜索一下。看到了这个setContentView方法之后,我就不跟大家互动了啊,直接捞干货说了啊。找到setContentView之后看到都是调用的这个有两个参数的setContentView(View view, ViewGroup.LayoutParams params)方法,那就没错了,就是他了。我们往下看,其它的不看就看重点,第一行代码,就是判断父容器是不是为空,为空的话就调用installDecor()方法。也就是说这个方法会返回顶级父容器,那么就点进去看看。
进去一看,前两行代码就为了实例化这个mDecor那这个mDecor就是个重点了。看看它是什么,上去一看是一个DecorView。看到这里可能有一部分人知道怎么回事了。没错,它就是PhoneWindow的第一层布局,我们自己布局的最顶层的父容器,它其实是个帧布局(可以进去看一下它是继承FragmeLayout的)。
找到这里还没有完事,还得找,找什么?找它还干了什么!
大家看如果DecorView不等于null会用这个mDecor返回一个mContentParent,那我们肯定要看看这个generateLayout方法干什么了呀。进去一看,妈呀,这个方法这么长,但是不要紧,看重点,先不管它这些判断,找我们的DecorView。
在这里!我们的DecorView在这里加了一个布局。那么要看看这个叫叫in的View什么样子啊。我就不上图了,直接口述了。这个in什么样,取决于layoutResource
所以就看这个LayoutResource什么样,这个layoutResource是在判断里面赋值的。我就不跟大家绕了,其实它的样子取决于你设置的theme,我们随便进去一个看一下↓
我相信大家看到这个图应该就恍然大悟了。
其实通过刚才的分析,android系统的布局应该是这样的。
本图是我自己画的,不对的地方希望大家指正。
其实就是在我们写布局的时候系统就把带有ActionBar的给我们了,然后我们的布局其实都是放在id为content的FragmeLayout的布局里面的。
好了 说到这里好像跑题了,但是其实是没跑题,我好像写的文章都带在跟大家一起来分享我对源码的认识,很多东西还得从源码看起啊。
好,下面开始撸起来!!!开始我们就说了要在最顶层的布局上添加我们的特效,那么我们首先要找到最顶层的布局,所以我写了一个查找到顶层容器的工具类。请看我的代码↓
/** * Created by 宋鑫 on 2017/1/5. * 工具类 */ public class BouncingMenuUtil { private ViewGroup mPatentVG;//这里就是顶级容器FragmeLayout private View rootView;//我们自己的布局文件(layout_rv_sweet.xml) private BouncingView bouncingv;//自己的自定义控件 private RecyclerView recyclerView;//显示数据的RecyclerView private MyRecyclerAdapter adapter; private BouncingMenuUtil(View v, int resId, MyRecyclerAdapter adapter) { this.adapter = adapter; //1.找到系统里的FragmentLayout mPatentVG = findrootParent(v); //渲染布局 rootView = LayoutInflater.from(v.getContext()).inflate(resId, null, false); bouncingv = (BouncingView) rootView.findViewById(R.id.sv); bouncingv.setCallback(new MyAnimationListener()); recyclerView = (RecyclerView) rootView.findViewById(R.id.rv); recyclerView.setLayoutManager(new LinearLayoutManager(v.getContext())); } /** * 调用这个方法返回一个BouncingMenuUtil对象 * @param v 传进来的自己的布局文件的最大的父容器,然后以这个容器为锚点向上查找,找到上一层的id为content的FrahmeLayout * @param resId 自己布局文件的id * @param adapter 这个就不用说了 就是制造好添加好数据的adapter * @return */ public static BouncingMenuUtil makeMenu(View v, int resId, MyRecyclerAdapter adapter) { return new BouncingMenuUtil(v, resId, adapter); } /** * 这个方法就是查找id为content的Fragment * 首先判断是不是FragmentLayout如果是的话判断id id正确就说明找到了 结束方法并返回 * @param v * @return */ private ViewGroup findrootParent(View v) { do { if (v instanceof FrameLayout) { if (v.getId() == android.R.id.content) return (ViewGroup) v; } [b]if (v != null) { ViewParent viewgp = v.getParent(); v = viewgp instanceof View ? (View) viewgp : null; } } while (v != null); return null; } public BouncingMenuUtil show() { //2.王帧布局里面添加自定义控件 if (rootView.getParent() != null) { //先将原来有的删除掉 再进行添加 mPatentVG.removeView(rootView); } //将自己的布局传入 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayoutCompat.LayoutParams.MATCH_PARENT, LinearLayoutCompat.LayoutParams.MATCH_PARENT); mPatentVG.addView(rootView, lp); //3.开始动画 bouncingv.show(); return this; } public void dismiss() { //消失就只用了一个位移动画,将控件移出屏幕之后然后再将控件销毁,避免内存泄漏 Log.d("sssss", "dismiss1111"); ObjectAnimator an = ObjectAnimator.ofFloat(rootView, "translationY", 0, rootView.getHeight()); an.setDuration(600); an.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mPatentVG.removeView(rootView); rootView = null; } }); an.start(); } public boolean ishow() { if (rootView == null) { return false; } return true; } /** * 回掉实现回调接口,主要是为了给recyclerView设置适配器 */ class MyAnimationListener implements BouncingView.AnimationListener { @Override public void onSrart() { } @Override public void onEnd() { } @Override public void onContentShow() { recyclerView.setVisibility(View.VISIBLE); recyclerView.setAdapter(adapter); recyclerView.scheduleLayoutAnimation(); } } }
这个类只是一个查找父容器和一些操作的工具类。要有上面哪个回弹的效果还得看我们的自定义控件。如果大家有一定的经验的话,一定就看得出自定义组件的头部分是用的贝塞尔曲线做的。如果不知道的也可以给我评论下,我再单独说一下也可以,在这里就不多说了,直接上代码了。
/** * Created by 宋鑫 on 2017/1/5. * 自定义控件 */ public class BouncingView extends View { private Paint mPaint; //变化的过程当中当前弧度的高度mArcHeight private int mArcHeight;//当前的弧高 private int mMaxArcHright;//狐高最大高度 private States mstate = States.NONE;//状态 private Path path = new Path();//需要绘制的路径 private AnimationListener animationListener;//回调接口 public enum States { NONE, STATUS_SMOOTH_UP, STATUS_DOWN, } public BouncingView(Context context) { super(context); init(); } public BouncingView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public BouncingView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } /** * 初始化颜色和弧度的最大值 */ private void init() { mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(getResources().getColor(android.R.color.white)); mMaxArcHright = getResources().getDimensionPixelSize(R.dimen.arc_max_height); } /** * 这个就不用说了吧 主要说下drawBG方法吧 * @param canvas */ @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawBG(canvas); } /** * 先看下这个方法 * @param canvas * 看完是不是想问是这比就修改了一次么?看到这里确实是修改了一次,但是我多调用几次不久好了么 * 所以我再show方法里面对控件的动画进行了监听啊 在AnimatorUpdateListener方法里面不断的调用了系统更新的方法啊 * 所以系统就会不断的调用onDraw方法。。。。。 */ private void drawBG(Canvas canvas) { int currentPointY = 0; path.reset(); //计算--不断的变化的高度 switch (mstate){ case NONE: currentPointY = 0; break; case STATUS_SMOOTH_UP: //currentPointY值--- 跟mArcHeighr的变化率差不多一样 currentPointY = (int)(getHeight()*(1-(float)mArcHeight/mMaxArcHright)+mMaxArcHright); break; case STATUS_DOWN: currentPointY = mMaxArcHright; break; } path.moveTo(0,currentPointY); path.quadTo(getWidth()/2,currentPointY-mArcHeight,getWidth(),currentPointY); path.lineTo(getWidth(),getHeight()); path.lineTo(0,getHeight()); path.close(); canvas.drawPath(path,mPaint); } /** * 不过多解释了 */ public void show() { mstate = States.STATUS_SMOOTH_UP; //这个就要是让RecyclerView进行显示数据的 if (animationListener!=null){ animationListener.onSrart(); this.postDelayed(new Runnable() { @Override public void run() { //显示数据 animationListener.onContentShow(); } },600); } ValueAnimator value = ValueAnimator.ofInt(0, mMaxArcHright); value.setDuration(800); value.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mArcHeight = (int) animation.getAnimatedValue(); if (mArcHeight == mMaxArcHright) { //弹一下 bounce(); } invalidate(); } }); value.setInterpolator(new AccelerateDecelerateInterpolator());//插值器(先快后慢) value.start(); } public void bounce(){ mstate = States.STATUS_DOWN; ValueAnimator value = ValueAnimator.ofInt( mMaxArcHright,0); value.setDuration(500); value.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mArcHeight = (int) animation.getAnimatedValue();//弧度不断减小 再不断更新 就会出现弹回去的效果了 invalidate(); } }); value.setInterpolator(new AccelerateDecelerateInterpolator()); value.start(); } public void setCallback(AnimationListener animationListener){ this.animationListener = animationListener; } public interface AnimationListener{ void onSrart(); void onEnd(); void onContentShow(); } }
总结一下:这篇博客的重点就是两个
1、怎么封装一个让别人用的很爽的工具类(Builder模式)
2、了解Android的布局文件结构
源码我总是会放到最后,因为我认为既然是来找东西的,肯定就是有欠缺的地方,所以我想让大家好好的看完文章。(其实我是想让大家跟我一起探讨学习,嘿嘿)
希望大家有时间多跟我交流
相关文章推荐
- ios 自定义弹出UIPickerView或UIDatePicker(动画效果)
- iOS学习之自定义弹出UIPickerView或UIDatePicker(动画效果)
- 21种ANDROID自定义DIALOG_动画弹出对话框效果组件
- dialog底部弹出自定义view并且伴随动画弹出和消失
- iOS学习之自定义弹出UIPickerView或UIDatePicker(动画效果)
- Android自定义弹出菜单+动画实现
- 自定义dialog和弹出dialog的动画
- iOS超实用的 自定义view的弹出动画
- android 自定义dialog弹出和消失动画
- android 自定义dialog弹出和消失动画
- android 自定义dialog弹出和消失动画
- 自定义ViewGroup,并添加高仿雪球app分享弹出动画。
- 【多级树形菜单-dialog自定义动画弹出方式-手势监听】手势监听
- android 自定义Toast增加点击事件、Toast弹出隐藏动画、Toast宽度为match_parent
- 自定义Android中Dialog的弹出动画
- 【多级树形菜单-dialog自定义动画弹出方式-手势监听】dialog自定义动画
- 自定义dialog占屏幕一半及从屏幕下方弹出动画
- Android 修改原生NumberPicker数字选择器的分隔线颜色、文字颜色和大小,同时利用PopupWindow和补间动画自定义弹出效果
- iOS学习之自定义弹出UIPickerView或UIDatePicker(动画效果)