近期博主在网上随便搜了一番,发现很多实现 底部弹出支付页面的大多数都用的,自定义PopupWindow 去实现的,里面复杂的逻辑看得我不想看,很多对自定义不熟悉伙伴们,看到 自定义 这三个字就有种血脉压制的感觉,别担心,博主以前也是和你们一样的,想弄好自定义还是要自己多多去动手实现去看看关于自定义的书籍,本次博主推荐一个用起来 非常舒服,颠覆传统自定义 的方式来做一个 底部弹出支付页面,想要做出非常友好的底部弹出支付页面,或者说其他华丽的动画弹出任意页面,那这篇文章不可错过哟!
先看效果图:
这里的显示效果,虽然是从底部弹出的,但弹出的方式是完全自己自定义实现的,不想其他方式实现的有诸多限制,该弹出动画你可以做成 天马行空 都没问题。
本章内容重点使用 GT库里 GT_View 封装类进行实现 自定义弹窗,
其实GT库中提供封装好的类 有很多:
这些都时候GT库提供封装的类,后续还会不定时添加的,本篇仅仅介绍 GT库中的 View 封装类的使用。
使用GT库里的,当然需要先依赖好GT库啦:
那咋们来看看,这个效果是怎么天马行空实现的吧,我们先把 样式、资源这些加上
资源:
round_whitelucency_bg13.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#008E8E8E"/>
<!-- 设置圆角 -->
<corners android:radius="1dp"/>
<!-- 设置边框 -->
<stroke
android:width="1dip"
android:color="#6B000000" />
</shape>
round_whitelucency_bg17.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFFF"/>
<!-- 设置圆角 -->
<corners
android:topRightRadius="15dp"
android:topLeftRadius="15dp"
/>
<!-- 设置边框 -->
<stroke
android:width="1dip"
android:color="#FFFFFF" />
</shape>
样式:
<style name="tv_pass">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">match_parent</item>
<item name="android:layout_weight">1</item>
<item name="android:gravity">center</item>
<item name="android:textColor">#000000</item>
<item name="android:background">#FFFFFF</item>
<item name="android:textStyle">bold</item>
<item name="android:textSize">28sp</item>
<item name="android:layout_marginEnd">2dp</item>
<item name="android:layout_marginBottom">2dp</item>
</style>
<style name="et_pass">
<item name="android:layout_width">50dp</item>
<item name="android:layout_height">50dp</item>
<item name="android:gravity">center</item>
<item name="android:maxLength">1</item>
<item name="android:inputType">numberPassword</item>
<item name="android:layout_marginStart">5dp</item>
<item name="android:background">@drawable/round_whitelucency_bg13</item>
<item name="android:textCursorDrawable">@drawable/color_cursor</item>
</style>
美化的样式资源图片就这些了,接下来附上
自定义 xml 布局:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/cl_filtrate"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="RtlSymmetry,LabelFor,NestedWeights,HardcodedText">
<View
android:id="@+id/view_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#70000000" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/ll_bottom1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@drawable/round_whitelucency_bg17"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="h,1:1">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:text="请输入二级密码"
android:textColor="#202020"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/view_line"
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_marginTop="15dp"
android:background="#EEEEEE"
app:layout_constraintTop_toBottomOf="@+id/tv_title" />
<!-- 密码框 -->
<LinearLayout
android:id="@+id/ll_pass"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:gravity="center"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@+id/view_line">
<EditText
android:id="@+id/et_1"
style="@style/et_pass"
android:focusable="true" />
<EditText
android:id="@+id/et_2"
style="@style/et_pass" />
<EditText
android:id="@+id/et_3"
style="@style/et_pass" />
<EditText
android:id="@+id/et_4"
style="@style/et_pass" />
<EditText
android:id="@+id/et_5"
style="@style/et_pass" />
<EditText
android:id="@+id/et_6"
style="@style/et_pass" />
</LinearLayout>
<TextView
android:id="@+id/tv_pass"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:padding="10dp"
android:text="忘记密码?请戳我"
android:textColor="#202020"
android:textSize="12sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@+id/ll_pass"
app:layout_constraintStart_toStartOf="@+id/ll_pass"
app:layout_constraintTop_toBottomOf="@+id/ll_pass" />
<View
android:id="@+id/view_line2"
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_marginTop="5dp"
android:background="#EEEEEE"
app:layout_constraintTop_toBottomOf="@+id/tv_pass" />
<LinearLayout
android:id="@+id/ll_bottom"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#EEEEEE"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/view_line2">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
style="@style/tv_pass"
android:text="1" />
<TextView
style="@style/tv_pass"
android:text="2" />
<TextView
style="@style/tv_pass"
android:text="3" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
style="@style/tv_pass"
android:text="4" />
<TextView
style="@style/tv_pass"
android:text="5" />
<TextView
style="@style/tv_pass"
android:text="6" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
style="@style/tv_pass"
android:text="7" />
<TextView
style="@style/tv_pass"
android:text="8" />
<TextView
style="@style/tv_pass"
android:text="9" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
style="@style/tv_pass"
android:background="#EEEEEE" />
<TextView
style="@style/tv_pass"
android:text="0" />
<LinearLayout
android:id="@+id/ll_delete"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_delete" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
布局效果图:
咋们再来看看自定义底部弹窗 核心代码:
//加载自定义xml布局
@GT.Annotations.GT_AnnotationView(R.layout.view_pay_pass)
public class PayPassView extends GT.GT_View.AnnotationView {
/**
* 必须写的
* @param context
* @param viewGroup 装入支付弹窗容器(后面会介绍使用的)
*/
public PayPassView(Context context, ViewGroup viewGroup) {
super(context, viewGroup);
}
private View view_bg;
private View ll_bottom1;
//需要重写的初始化方法
@Override
protected void initView(View view) {
super.initView(view);
//获取组件
view_bg = findViewById(R.id.view_bg);
ll_bottom1 = findViewById(R.id.ll_bottom1);
//单击事件
ll_bottom1.setOnClickListener(null);//消耗掉点击密码框的单击事件
view_bg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
hide();//单击上部分 半透明黑背景 进行隐藏动画操作
}
});
}
//显示动画
public void show() {
view_bg.setVisibility(View.VISIBLE);
ll_bottom1.setVisibility(View.VISIBLE);
}
//隐藏动画
private void hide() {
view_bg.setVisibility(View.GONE);
ll_bottom1.setVisibility(View.GONE);
}
}
看到上面的实现方法,够简单吧,唯一有疑惑的应该就只有 加载自定义xml布局 了吧,这个加载自定义xml布局是 GT库里非常基础的一个实现 加载布局 的方式,上面展示的是 GT库 加载布局 版本的第二版,一般博主都不用的淘汰版,不是不好用,还是有更方便的版本,博主都用 GT库 加载布局 第四版,想要了解更多GT库加载布局的方式我放下面了:
接下来咋们再来看看 调用 支付弹窗页面的布局:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开始支付"
android:background="@drawable/bg_black_et"
app:layout_constraintVertical_bias="0.28"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:paddingStart="15dp"
android:paddingEnd="15dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<RelativeLayout
android:id="@+id/rl"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout_editor_absoluteX="16dp"
tools:layout_editor_absoluteY="231dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
里面的 bg_black_et.xml 是GT库 提供的,可以直接使用
MainActivity.java
public class MainActivity extends AppCompatActivity {
private PayPassView payPassView;
private RelativeLayout rl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rl = findViewById(R.id.rl);//获取组件容器
//构建 弹窗支付页面 并将构建好的支付页面 添加到 容器 rl 中
payPassView = new PayPassView(this,rl);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
payPassView.show();//显示支付页面
}
});
}
}
这个简单吧,其实就是将我们创建好的 支付弹窗添加到 容器中。写到这里就可以直接运行看看效果了:
当前代码运行 效果图:
效果都出来了吧,看到这里是不是感觉,添加一个支付页面很简单呢,这就是完全的自定义布局页面,这些家人们不用再对自定义有所顾忌了吧,
GT库为简单而生
做到这,效果是出来了,但是不是看到动画很僵硬,而且点击数字和回退按钮都没反应,那是肯定的,咱们的核心代码里啥逻辑都没有,肯定没啥反应不,接下来,博主就来接着完善剩余的逻辑处理,先把这个第二版 加载布局给换成 第四版 的,第四版需要加上 gt-DataBinding 功能,
供复制:
//注册 gt-DataBinding 功能
annotationProcessor 'com.github.1079374315:GSLS_Tool:v1.4.2.4'
将红色框框里的复制粘贴注册好就可以使用第四版 加载布局了
添加注册好后,咋们来将核心代码改改:
继承 PayPassViewBinding 类时 会爆红,因为这个类时编译后才会自动产生的
核心代码:
//使用 GT_DataBinding 功能
@GT_DataBinding(setLayout = "view_pay_pass", setBindingType = GT_DataBinding.View)
@GT.Annotations.GT_AnnotationView(R.layout.view_pay_pass)//加载自定义xml布局
public class PayPassView extends PayPassViewBinding {
public PayPassView(Context context, ViewGroup viewGroup) {
super(context, viewGroup);
}
@Override
protected void initView(View view) {
super.initView(view);
ll_bottom1.setOnClickListener(null);//消耗单击密码框事件
hide();//装入容器后直接隐藏
}
//设置单击事件
@GT.Annotations.GT_Click({R.id.view_bg, R.id.tv_pass})
public void onCLick(View view) {
switch (view.getId()) {
case R.id.tv_pass://忘记密码
GT.toast(context, "单击了忘记密码");//吐司
break;
case R.id.view_bg:
hide();//单击上部分 半透明黑背景 进行隐藏动画操作
break;
}
}
//显示动画
public void show() {
view_bg.setVisibility(View.VISIBLE);
ll_bottom1.setVisibility(View.VISIBLE);
}
//隐藏动画
private void hide() {
view_bg.setVisibility(View.GONE);
ll_bottom1.setVisibility(View.GONE);
}
}
原来的核心代码:
这样一对比是不是感觉 比之前的代码简洁了很多
你再运行看看,和原来的效果是一样的,接下来咋们后续都是以第四版来添加后续的逻辑处理了
想要添加动画,那博主第一想的就是 GT库内的 强大的动画库 操作简单,注释明了
核心代码修改:
//使用 GT_DataBinding 功能
@GT_DataBinding(setLayout = "view_pay_pass", setBindingType = GT_DataBinding.View)
@GT.Annotations.GT_AnnotationView(R.layout.view_pay_pass)//加载自定义xml布局
public class PayPassView extends PayPassViewBinding {
public PayPassView(Context context, ViewGroup viewGroup) {
super(context, viewGroup);
}
@Override
protected void initView(View view) {
super.initView(view);
ll_bottom1.setOnClickListener(null);//消耗单击密码框事件
hide();//装入容器后直接隐藏
}
//设置单击事件
@GT.Annotations.GT_Click({R.id.view_bg, R.id.tv_pass})
public void onCLick(View view) {
switch (view.getId()) {
case R.id.tv_pass://忘记密码
GT.toast(context, "单击了忘记密码");//吐司
break;
case R.id.view_bg:
hide();//单击上部分 半透明黑背景 进行隐藏动画操作
break;
}
}
//构建GT动画库
private static final GT.GT_Animation animation = new GT.GT_Animation();
//定义 必要的计算参数值
private int height;
//显示动画
public void show() {
//获取容器的这个View的宽高,好设置动画的动态数据
height = view_bg.getHeight();
//使用GT库 移动 Y轴(上下) 动画,来给 组件 ll_bottom1 设置移动
animation.translateY_T(height, 0, 300, 0, false, ll_bottom1);
view_bg.setVisibility(View.VISIBLE);//显示半透明黑色背景
}
//隐藏动画
private void hide() {
//获取容器的这个View的宽高,好设置动画的动态数据
height = view_bg.getHeight();
if (height == 0) {//在刚开始创建的时候获取的高会为0,所以直接隐藏支付页
animation.translateY_T(0, 3000, 1, 0, false, ll_bottom1);
}else{//后续就更具动态高度进行动画展示
animation.translateY_T(0, height, 300, 0, false, ll_bottom1);
}
view_bg.setVisibility(View.INVISIBLE);//隐藏半透明黑色背景
}
}
效果图:
这时运行显示支付页面就有 底部弹出效果了,使用 GT 动画库 里的 Y轴移动 动画就可以很好的达到底部弹出的效果,而且使用非常简单, GT库的参数不会的小伙伴们可以,自己去对着 形参注释 一个一个参数试,注释也很人性:
接下来,博主就直接粘贴所有逻辑处理核心代码了,不再做过多的解释
最终核心代码:
@GT_DataBinding(setLayout = "view_pay_pass", setBindingType = GT_DataBinding.View)
@GT.Annotations.GT_AnnotationView(R.layout.view_pay_pass)
public class PayPassView extends PayPassViewBinding {
private int height;
private int width;
private List<EditText> editTextList;
private int index;//索引
private static GT.GT_Animation animation = new GT.GT_Animation();
public PayPassView(Context context, ViewGroup viewGroup) {
super(context, viewGroup);
}
@Override
protected void initView(View view) {
super.initView(view);
hide();
ll_bottom1.setOnClickListener(v -> {
});
editTextList = new ArrayList<>();
}
@Override
public void loadData(View view) {
super.loadData(view);
//设置第一个密码框光标
GT.Thread.getInstance(0).execute(() -> {
GT.Thread.sleep(300);
index = 0;
GT.Thread.runAndroid(() -> et_1.requestFocus());
});
//设置每个单独密码的事件
for (int i = 0; i < ll_pass.getChildCount(); i++) {
EditText editText = (EditText) ll_pass.getChildAt(i);
editText.setTag(i);//设置吗每个 EditText 索引
editTextList.add(editText);
int finalI = i;
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
String s1 = editText.getText().toString();
editText.setSelection(s1.length());
if (s1.length() == 0) {//删除的时候
index = Integer.parseInt(editText.getTag().toString());
if (index - 1 >= 0) {
EditText editText1 = editTextList.get(index - 1);
editText1.requestFocus();
}
if (index > 0)
index--;
} else if (s1.length() == 1) {//输入的时候
if (finalI + 1 <= editTextList.size() - 1) {
EditText editText1 = editTextList.get(finalI + 1);
editText1.requestFocus();
index = Integer.parseInt(editText1.getTag().toString());
}
}
transactionPasswordAuthentication();
}
//验证交易密码是否全部输入
private void transactionPasswordAuthentication() {
boolean isOk = true;
String apppwd = "";
for (int i = 0; i < editTextList.size(); i++) {
EditText editText1 = editTextList.get(i);
String s = editText1.getText().toString();
if (s.length() == 0) {
isOk = false;
}
apppwd += s;
}
if (!isOk) return;
// AppUtils.toast("正在支付中...");
for (int i = 0; i < editTextList.size(); i++) {
EditText editText1 = editTextList.get(i);
editText1.setText("");
}
//这里的支付密码可以通过接口返回数据 或者 GT.EventBus 传递消息出去
GT.logt("支付密码:" + apppwd);
GT.toast(context, "支付密码:" + apppwd);
hide();
}
});
}
//单击密码设置
for (int i = 0; i < ll_bottom.getChildCount(); i++) {
View childAt = ll_bottom.getChildAt(i);
if (childAt instanceof ViewGroup) {
ViewGroup vg = (ViewGroup) childAt;
for (int j = 0; j < vg.getChildCount(); j++) {
View childAt1 = vg.getChildAt(j);
//输入密码
if (childAt1 instanceof TextView) {
TextView tv = (TextView) childAt1;
tv.setOnClickListener(v -> {
EditText editText = editTextList.get(index);
editText.setText(tv.getText().toString());
});
}
//删除单个密码框的操作
if (childAt1 instanceof LinearLayout) {
childAt1.setOnClickListener(v -> {
EditText editText = editTextList.get(index);
editText.setText("");
});
}
}
}
}
}
//单击事件
@GT.Annotations.GT_Click({R.id.view_bg, R.id.tv_pass})
public void onCLick(View view) {
switch (view.getId()) {
case R.id.tv_pass://忘记密码
GT.toast(context, "单击了忘记密码");
break;
case R.id.view_bg:
hide();
break;
}
}
//显示动画
public void show() {
//设置第一个密码框光标
GT.Thread.getInstance(0).execute(() -> {
GT.Thread.sleep(300);
index = 0;
GT.Thread.runAndroid(() -> {
if (et_1 != null) et_1.requestFocus();
});
});
height = view_bg.getHeight();
width = view_bg.getWidth();
animation.translateY_T(height, 0, 300, 0, false, ll_bottom1);
view_bg.setVisibility(View.VISIBLE);
GT.Thread.getInstance(0).execute(() -> {
for (float a = 0; a < 1; ) {
if (view_bg == null) break;
a += 0.01;
GT.Thread.sleep(1);
float finalA = a;
GT.Thread.runAndroid(() -> {
if (view_bg != null) {
view_bg.setAlpha(finalA);
}
});
}
});
}
//隐藏动画
private void hide() {
//清空密码框剩余的数据
GT.Thread.getInstance(0).execute(() -> GT.Thread.runAndroid(() -> {
for (int i = 0; i < editTextList.size(); i++) {
EditText editText1 = editTextList.get(i);
if (editText1 == null) return;
editText1.setText("");
}
}));
height = view_bg.getHeight();
width = view_bg.getWidth();
if (height == 0) {
animation.translateY_T(0, 3000, 1, 0, false, ll_bottom1);
view_bg.setAlpha(0);
view_bg.setVisibility(View.INVISIBLE);
} else {
animation.translateY_T(0, height, 300, 0, false, ll_bottom1);
GT.Thread.getInstance(0).execute(() -> {
for (float a = 1; a > 0; ) {
a -= 0.01;
GT.Thread.sleep(1);
float finalA = a;
GT.Thread.runAndroid(() -> {
if (view_bg != null) {
view_bg.setAlpha(finalA);
view_bg.setVisibility(View.INVISIBLE);
}
});
}
});
}
}
}
本篇重点再介绍 GT_View 封装类,当你学会 GT_View 封装类,那自定义其他弹窗岂不是手到擒来,再加上 熟练使用 GT库动画库,那 左弹、右弹、上跳、下跳、啥天马行空动画不能做,GT库里好包括 Z 轴动画,将这些动画组合起来可以直接产生 3D 效果哦。
本章GT_View + GT动画库,实现的 自定义 底部支付弹窗
点个关注点个赞呗(〃'▽'〃) 关注博主最新发布库:GitHub - 1079374315/GT