先来简单回顾下建造者模式
定义
把一个复杂对象表示和构建过程分离开来,使得同样的构建过程可以创建不同的表示
场景
- 相同方法,不同的执行顺序,产生不同的事件结果
- 多个部件可以装配到一个对象,但是产生的运行结果又不相同时
- 产品类复杂,或者,产品类调用顺序不同产生不同作用
- 当初始化一个对象很复杂,如参数多,且很多参数有默认值
UML结构图
- 产品类:一般是一个较为复杂的对象,也就是说创建对象的过程比较复杂,一般会有比较多的代码量。在本类图中,产品类是一个具体的类,而非抽象类。实际编程中,产品类可以是由一个抽象类与它的不同实现组成,也可以是由多个抽象类与他们的实现组成。
- 抽象建造者:引入抽象建造者的目的,是为了将建造的具体过程交与它的子类来实现。这样更容易扩展。一般至少会有两个抽象方法,一个用来建造产品,一个是用来返回产品。
- 建造者:实现抽象类的所有未实现的方法,具体来说一般是两项任务:组建产品;返回组建好的产品。
- 导演类:负责调用适当的建造者来组建产品,导演类一般不与产品类发生依赖关系,与导演类直接交互的是建造者类。一般来说,导演类被用来封装程序中易变的部分。
实际开发过程,Director角色经常会被忽略,直接使用Builder进行对象的组装,Builder通常为链式调用,结构更简单,对产品有更精细的控制
优点
- 良好的封装性,客户端不必关心产品内部组成细节
- 建造者独立,易扩展
缺点
产生多余的Builder对象以及Direactor对象,消耗内存
通用弹窗的封装
弹窗的场景符合建造者模式场景4,所以这边使用建造者模式封装此(省略导演类和抽象构造者类),先看如下调用代码
MainActivity.java
package cn.llj.pop.demo;
import android.app.Activity;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity
{
private MyPopWindow mPopWindow;
private View mPopView;
private TextView mPopTv;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPopView = View.inflate(this, R.layout.pop_view, null);
mPopTv=mPopView.findViewById(R.id.tv);
mPopTv.setOnClickListener(new View.OnClickListener()//点击弹窗内的控件,弹窗消失
{
@Override
public void onClick(View view)
{
if (mPopWindow !=null && mPopWindow.isShowing())
mPopWindow.dismiss();
}
});
}
public void showPop(View view)
{
if (mPopWindow != null)
mPopWindow.show();
else
{ //这里参数如果与默认值一致,无需再设置
mPopWindow = new MyPopWindow.Builder(this)
.setLayoutView(mPopView)//设置弹窗布局
.setOutsideTouchable(true)//弹窗外是否可点击是否消失
.setBgTranslucent(true)//弹窗外是否半透明
.setHideStateBar(false)//是否隐藏状态栏
.setAnimStyle(R.style.pop_anim_style)//设置弹窗动画
.setHeight(400)//设置弹窗高度 单位px
.setWidth(ViewGroup.LayoutParams.MATCH_PARENT)//设置弹窗宽度,与屏幕同宽
.setOrientation(Gravity.BOTTOM)//弹窗出现的方向,Gravity.BOTTOM 底部弹出,Gravity.RIGHT 右侧弹出
.setPopDismissListener(new MyPopWindow.OnPopDismissListener()//设置弹窗关闭回调
{
@Override
public void onDismiss()
{
Toast.makeText(MainActivity.this,"弹窗关闭了",Toast.LENGTH_SHORT).show();
}
})
.build();
mPopWindow.show();
// mPopWindow.show(10,20); //设置弹窗距离屏幕的距离
}
}
@Override
protected void onDestroy()
{
if (mPopWindow != null)
mPopWindow.destroy();//弹窗销毁,防止内存泄漏
super.onDestroy();
}
}
- 自定义弹窗类
MyPopWindow.java
package cn.llj.pop.demo;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.PopupWindow;
/**
* Created by llj on 2019/4/2.
* desc:适用于该项目的通用底部弹出的pop,有需要拓展的设置,在此类拓展
*/
public class MyPopWindow implements PopupWindow.OnDismissListener, ValueAnimator
.AnimatorUpdateListener
{
private final int ALPHA_ANIMATOR_DURATION = 200;//半透明动画消失时间
private static boolean mOutsideTouchable = true;
private static boolean mFocusable = true;
private static boolean mIsHideStateBar = false;
private static View mLayoutView;
private static int mWidth = ViewGroup.LayoutParams.MATCH_PARENT;//px
private static int mHeight = ViewGroup.LayoutParams.MATCH_PARENT;//px
private static int mAnimStyle = R.style.pop_anim_style;
private PopupWindow mPopWindow;
private ValueAnimator mBgAlphaAnimator;
private static Context mContext;
private static OnPopDismissListener mPopDismissListener;
private static boolean mIsBgTranslucent = true;
private static int mOrientation = Gravity.BOTTOM;
public MyPopWindow()
{
mPopWindow = new PopupWindow(mLayoutView, mWidth, mHeight);
mPopWindow.setAnimationStyle(mAnimStyle);
mPopWindow.setFocusable(mFocusable);
mPopWindow.setOutsideTouchable(mOutsideTouchable);
mPopWindow.setOnDismissListener(this);
}
@Override
public void onAnimationUpdate(ValueAnimator animation)
{
if (mContext != null)
{
((Activity) mContext).getWindow().getAttributes().alpha = (float) animation
.getAnimatedValue();
((Activity) mContext).getWindow().setAttributes(((Activity) mContext).getWindow()
.getAttributes());
}
}
@Override
public void onDismiss()
{
if (mIsBgTranslucent)
{
mBgAlphaAnimator = ValueAnimator.ofFloat(0.5f, 1.0f);
mBgAlphaAnimator.start();
mBgAlphaAnimator.setDuration(ALPHA_ANIMATOR_DURATION);
mBgAlphaAnimator.addUpdateListener(this);
}
if (mPopDismissListener != null)
mPopDismissListener.onDismiss();
}
public void show()
{
if (mPopWindow != null && mLayoutView != null && !mPopWindow.isShowing())
{
setAnimAndStateBar();
mPopWindow.showAtLocation(mLayoutView, mOrientation, 0, 0);
}
}
/**
* 相对于屏幕的距离
* @param xoff
* @param yoff
*/
public void show(int xoff, int yoff)
{
if (mPopWindow != null && mLayoutView != null && !mPopWindow.isShowing())
{
setAnimAndStateBar();
mPopWindow.showAtLocation(mLayoutView, mOrientation, xoff, yoff);
}
}
private void setAnimAndStateBar()
{
if (mIsBgTranslucent)
{
mBgAlphaAnimator = ValueAnimator.ofFloat(1.0f, 0.5f);
mBgAlphaAnimator.setDuration(ALPHA_ANIMATOR_DURATION);
mBgAlphaAnimator.start();
mBgAlphaAnimator.addUpdateListener(this);
}
if (mIsHideStateBar)
{
((Activity) mContext).getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
}
/**
* 弹窗关闭
*/
public void dismiss()
{
if (mPopWindow != null)
mPopWindow.dismiss();
}
/**
* 弹窗销毁,防止内存泄漏
*/
public void destroy()
{
if (mPopWindow.isShowing())
mPopWindow.dismiss();
mPopWindow = null;
mContext = null;
mBgAlphaAnimator = null;
}
public boolean isShowing()
{
return mPopWindow != null && mPopWindow.isShowing();
}
/**
* Listener that is called when this popup window is dismissed.
*/
public interface OnPopDismissListener
{
/**
* Called when this popup window is dismissed.
*/
void onDismiss();
}
public static class Builder
{
public Builder(Context context)
{
if (context == null || !(context instanceof Activity) || ((Activity) context)
.isFinishing())
{
throw new RuntimeException("activity is invalidate which can not create a " +
"popupwindow:" + context);
}
mContext = context;
}
/**
* 弹窗出现的方向
*
* @param orientation Gravity.BOTTOM Gravity.RIGHT
* @return
*/
public Builder setOrientation(int orientation)
{
mOrientation = orientation;
return this;
}
public Builder setFocusable(boolean focusable)
{
mFocusable = focusable;
return this;
}
/**
* 是否隐藏状态栏
*
* @param hideStateBar
* @return
*/
public Builder setHideStateBar(boolean hideStateBar)
{
mIsHideStateBar = hideStateBar;
return this;
}
public Builder setOutsideTouchable(boolean outsideTouchable)
{
mOutsideTouchable = outsideTouchable;
return this;
}
public Builder setLayoutView(View layout)
{
mLayoutView = layout;
return this;
}
public Builder setWidth(int width)
{
mWidth = width;
return this;
}
public Builder setHeight(int height)
{
mHeight = height;
return this;
}
/**
* 设置出现消失动画,默认自下而上
*
* @param animStyle
* @return
*/
public Builder setAnimStyle(int animStyle)
{
mAnimStyle = animStyle;
return this;
}
public Builder setBgTranslucent(boolean isBgTranslucent)
{
mIsBgTranslucent = isBgTranslucent;
return this;
}
public Builder setPopDismissListener(OnPopDismissListener listener)
{
mPopDismissListener = listener;
return this;
}
public MyPopWindow build()
{
return new MyPopWindow();
}
}
}
- 这里预置几种动画类型,根据实际业务自行拓展
style.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
<!-- Customize your theme here. -->
</style>
<!--底部弹出动画-->
<style name="pop_anim_style" parent="android:Animation">
<item name="android:windowEnterAnimation">@anim/pop_enter_anim</item>
<item name="android:windowExitAnimation">@anim/pop_exit_anim</item>
</style>
<!--右侧弹出动画-->
<style name="pop_right_anim_style" parent="android:Animation">
<item name="android:windowEnterAnimation">@anim/pop_right_enter_anim</item>
<item name="android:windowExitAnimation">@anim/pop_right_exit_anim</item>
</style>
<!--右上角缩放弹出动画-->
<style name="pop_fade_anim_style">
<item name="android:windowEnterAnimation">@anim/pop_fade_in</item>
<item name="android:windowExitAnimation">@anim/pop_fade_out</item>
</style>
</resources>
- 相关动画文件
pop_enter_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromYDelta="100%"
android:toYDelta="0" />
</set>
pop_exit_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromYDelta="0"
android:toYDelta="100%" />
</set>
pop_fade_in.xml
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromXScale="0.001"
android:toXScale="1.0"
android:fromYScale="0.001"
android:toYScale="1.0"
android:pivotX="100%"
android:pivotY="10%"
android:duration="200" />
pop_fade_out.xml
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromXScale="1.0"
android:toXScale="0.001"
android:fromYScale="1.0"
android:toYScale="0.001"
android:pivotX="100%"
android:pivotY="10%"
android:duration="200" />
pop_right_enter_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromXDelta="100%"
android:toXDelta="0" />
<!-- <alpha
android:duration="200"
android:fromAlpha="0.0"
android:toAlpha="1.0" />-->
</set>
pop_right_exit_anim.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="200"
android:fromXDelta="0"
android:toXDelta="100%" />
</set>
Android源码中的建造者模式
- AlertDialog
如下为AlertDialog源码结构:
具体细节可自行查看源码,本文的例子相当于AlertDialog的简易版
- 图片加载框架Glide、ImageLoader等