您的位置:首页 > 产品设计 > UI/UE

高逼格动画---自定义弹出式窗口+优化

2017-01-06 15:08 399 查看
今天给大家带来的给最底层的布局添加控件的原理分析。首先看下效果吧↓



(我是给第一个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的布局文件结构

源码我总是会放到最后,因为我认为既然是来找东西的,肯定就是有欠缺的地方,所以我想让大家好好的看完文章。(其实我是想让大家跟我一起探讨学习,嘿嘿)

希望大家有时间多跟我交流
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 高级UI