一. 前言

Android中对于下面效果的页面控制器的应用场景还是比较多的,最典型的例子就是引导图和轮播图。接下来我们就通过自定义View完成下面的效果,并且做成静态库,方便日后使用。

Github地址:PageController

二. 自定义View

1. 自定义需要用到的属性

首先,我们创建一个继承于LinearLayout的PageController控件。然后在其中定义需要用到的属性。

1
2
3
4
5
6
7
8
9
10
11
//页面数量
private int numberOfPage;

//页面之间的距离
private int pagePadding;

//记录当前记录的是第几页
private int currentPage;

//页面的样式
private int pageResource;

2. 在values文件夹下自定义属性

由于定义的属性在系统默认的属性里面没有,所以我们在values文件夹下创建了xml文件用来自定义属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="PagerController">
<!--页面的总数-->
<attr name="numberOfPage" format="integer"/>
<!--当前处于那一页-->
<attr name="currentPage" format="integer"/>
<!--页面之间的间距-->
<attr name="pagePadding" format="integer"/>
<!--页面的样式,包括选中和没有选中-->
<attr name="pageResource" format="reference"/>
</declare-styleable>
</resources>

3. 创建构造方法

由于有四个构造方法,所以我们需要创建一个公共的方法用来初始化一些公共的东西。

1
2
3
4
5
6
7
8
9
10
/**
* 初始化方法
*/
private void init() {
//设置子View水平排列
this.setOrientation(LinearLayout.HORIZONTAL);

//设置子View水平垂直居中
this.setGravity(Gravity.CENTER);
}

java代码创建该类的构造方法不用多做什么,我们将大部分的任务交给定义的属性的set方法。xml代码创建该类的构造方法需要解析自定义的属性值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 构造方法 Xml代码创建该类的时候会调用该方法
* @param context
* @param attrs
*/
public PagerController(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);

//初始化
init();

//取出自定义的属性
if (attrs != null){
//1.获得自定义属性的集合
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PagerController);

//2.取出资源并设置
//注意: setNumberOfPage 必须最后调用
//原因: 内部需要pagePadding,pageResource
pagePadding = typedArray.getInteger(
R.styleable.PagerController_pagePadding,
20);
pageResource = typedArray.getResourceId(
R.styleable.PagerController_pageResource,
0);
currentPage = typedArray.getInteger(
R.styleable.PagerController_currentPage,
0);
this.setNumberOfPage(
typedArray.getInteger(
R.styleable.PagerController_numberOfPage,
0)
);

//释放资源
typedArray.recycle();
}
}

4. 创建页面控制点

由于我们对于每一个页面控制点的大小都是设置的wrap_content,而我们又设置了BackgroundResource,所以页面控制点的大小在传入的pageResource中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void setNumberOfPage(int numberOfPage) {
this.numberOfPage = numberOfPage;

//创建页面
for (int i = 0; i < numberOfPage; i++) {
//1.创建一个视图
ImageView dot = new ImageView(getContext());
//2.设置样式
dot.setBackgroundResource(this.getPageResource());
//3.设置约束 大小交给样式文件
LayoutParams layoutParams = new LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
if (i > 0) {
layoutParams.leftMargin = this.getPagePadding();
} else {
//默认选中第一个点
dot.setEnabled(false);
}

//加入到布局中
this.addView(dot,layoutParams);
}
}

5. 设置当前页面以及左右切换

实现左右切换就是判断触摸点在视图的左半边还是右半边,然后确定好页面值,调用setCurrentPage。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 触摸事件
*/
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
//如果是按下事件
if (event.getAction() == MotionEvent.ACTION_DOWN) {
//获取触摸点的横坐标
float x = event.getX();

//判断是否切换页面
if (x > this.getWidth() / 2.0){
//向右切换
this.setCurrentPage((currentPage+1) % numberOfPage);
}else {
//向左切换
this.setCurrentPage((currentPage-1+numberOfPage) % numberOfPage);
}

//回调事件
if (this.pageChangeListener != null) {
pageChangeListener.pageHasChange(this.currentPage);
}
}

//消费事件
return true;
}

在此方法中是将新的页面控制点替换掉旧的页面控制点,所以我们可以获取这两个控制点进而设置相关显示的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void setCurrentPage(int currentPage) {
//1.将上一个页面还原为默认状态
ImageView last_dot = (ImageView) this.getChildAt(this.currentPage);
last_dot.setEnabled(true);

//2.改变当前页面
this.currentPage = currentPage;

//3.将当前点设置为选中状态
ImageView current_dot = (ImageView) this.getChildAt(this.currentPage);
current_dot.setEnabled(false);

//4.开启动画
if (this.pageChangeAnimation != null) {
pageChangeAnimation.changeAnimation(last_dot, current_dot);
}
}

6. 设置页面回调

创建用于回调当前页面值的接口,我们在监听值改变的类中实现该接口,实现对应的方法,并设置监听者是自己。

1
2
3
4
5
6
7
8
9
10
/**
* 接听接口-监听页面的切换
*/
public interface PageChangeListener {
//回调当前的页面
void pageHasChange(int currentPage);
}

//页面切换的监听者
private PageChangeListener pageChangeListener;

7. 设置页面切换动画

创建页面切换动画的接口,在其中定义切换动画的方法。将该方法交给外部来具体实现。

1
2
3
4
5
6
7
8
9
10
/**
* 动画接口-动画是如何切换的
*/
public interface PageChangeAnimation {
//页面切换的动画
void changeAnimation(View last_dot, View current_dot);
}

//页面切换的动画
private PageChangeAnimation pageChangeAnimation;

三. 具体的使用

1. 准备页面控制点的样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

<!--enable:false的情况-->
<item android:state_enabled="false">
<shape android:shape="oval">
<!--大小-->
<size android:width="40dp"
android:height="40dp"
/>

<!--颜色-->
<solid android:color="#ff0000"/>
</shape>
</item>

<!--enable:true的情况,也是默认的情况-->
<item android:state_enabled="true">
<shape android:shape="oval">
<!--大小-->
<size android:width="40dp"
android:height="40dp"
/>

<!--颜色-->
<solid android:color="#666666"/>
</shape>
</item>
</selector>

2. 监听类实现回调接口以及设置切换动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<swu.xl.pagecontroller.PagerController
android:id="@+id/page_controller"
android:layout_centerInParent="true"
android:background="#000000"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:numberOfPage="5"
app:pageResource="@drawable/page_controller_shape"
app:pagePadding="30"
android:paddingTop="20dp"
android:paddingBottom="20dp"
/>

</RelativeLayout>

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//初始化
PagerController pagerController = findViewById(R.id.page_controller);

//设置动画
pagerController.setPageChangeAnimation(new PagerController.PageChangeAnimation() {
@Override
public void changeAnimation(View last_dot, View current_dot) {
//上一个点不做动画

//针对当前点的动画
ObjectAnimator scale = ObjectAnimator.ofFloat(current_dot, "scaleX", 1.0f, 1.2f, 1.0f);
scale.setDuration(500);
scale.setInterpolator(new BounceInterpolator());
scale.start();
}
});

//监听页面切换
pagerController.setPageChangeListener(new PagerController.PageChangeListener() {
@Override
public void pageHasChange(int currentPage) {
System.out.println("当前页面:"+(currentPage+1));
}
});
}
}

3. 运行效果

四. 制作静态库

详细步骤可以参考:android制作依赖库上传github并公开使用

1. 创建一个Android Library模块

创建一个Android Library模块,将在app模块测试的PageController类以及自定义属性的xml文件复制到新建的Android Library模块内。

2. 将项目提交到Github

使用Android Studio将该项目提交到Github上。

具体内容可以参考:AndroidStudio-提交&更新代码到Github上的远程仓库

3. 创建依赖库的版本进行发布

填写的 Tag Version 很重要,下一步会用到。

4. 获取依赖地址

打开 JitPack,输入Github项目地址,点击Look up,即可查到Library的版本号,也就是我们上一步填写的。往下浏览便可以看到如何添加依赖,记住需要替换其中的Tag。

1
2
3
4
//实际使用需要写成这样
dependencies {
implementation 'com.github.xiaoshitounen:PageController:1.0.0'
}

5. 注意

在项目中同步的时候可能会报错:

1
2
3
ERROR: Failed to resolve: XXX 
Show in Project Structure dialog
Affected Modules: app

错误可能原因是:

① Tag值没有替换或者不是最新的。参考:AS(android studio) 添加第三方库时报,Error: Failed to resolve: com.github Affected Modules 解决办法

② 依赖位置添加错误,maven依赖需要放在allprojects中。参考:ERROR: Failed to resolve: com.github.lzyzsd:jsbridge:1.0.4 Show in Project Structure dialog Affected

③ 运行的版本和静态库额版本不一致。参考:Failed to resolve:com.android.support:appcompat-v7:报错处理

参考文章

实用——编写 Android Library 的最佳实践