下面是详细讲解 "Android自定义ViewGroup实现选择面板" 的完整攻略。
1. 前言
在日常的 App 开发中,我们经常会对页面进行布局的调整,比如添加选择面板。而这时,我们往往无法使用 Android 原生提供的布局组件来实现,因为我们需要的是一个自定义的布局组件,来实现我们自己的需求。
因此,通过本文,你将能够学习到如何自定义一个针对特定功能的 View,并将其实现到自己的项目中。具体来说,我们将学习如何实现一个选择面板(Picker),它类似于 iOS 上的选择面板,可以让用户在其中选择一个或多个项目。
2. 选择面板的实现思路
在实现选择面板时,我们需要做以下几个步骤:
- 自定义 View 来实现选择面板;
- 定义选择面板中所包含的子 View(比如 TextView、LinearLayout、RecyclerView 等);
- 实现选择面板的滚动效果;
- 处理用户的触摸事件,实现手势滑动;
- 实现选择面板的数据绑定。
本文将着重介绍第 1 步和第 4 步,在对话中,我将仅提供第 1 步的详细介绍。
3. 自定义 View
为了实现选择面板,我们首先需要自定义一个 View。具体来说,我们需要继承 ViewGroup 类,并重写父类 View 中的以下方法:
public PickerPanelView(Context context) {
super(context);
init();
}
public PickerPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PickerPanelView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mScroller = new Scroller(getContext());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 定义选择面板所占用的大小
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 留空
}
在上面的代码中,我们首先继承了 ViewGroup 类,并重写了父类 View 中的 3 个构造函数,以及 init()、onMeasure() 和 onLayout() 3 个方法。具体介绍如下:
- 构造函数:这 3 个函数分别用于在不同的环境下创建 View 的实例对象,其中最常用的是第 2 个,即
PickerPanelView(Context context, AttributeSet attrs)
。在该函数中,我们调用了init()
方法,用于初始化选择面板控件。 - init() 方法:该方法主要用于对选择面板控件进行初始化,其中我们创建了一个 Scroller 对象。
- onMeasure() 方法: 该方法主要用于为选择面板控件指定尺寸大小。在该方法中,我们调用了父类 View 中的
setMeasuredDimension()
方法,来指定选择面板的大小。 - onLayout() 方法: 该方法主要用于定义选择面板中各个子 View(比如 TextView、LinearLayout、RecyclerView 等)的位置和大小。在本例中,我们留空该方法。
注意:在自定义 View 时,我们需要手动为控件指定尺寸大小,否则将无法正确地显示在屏幕上。
到这里,我们已经完成了自定义 View 的基本工作,现在可以在 Activity 中使用该组件了。具体参考下面的示例代码。
// 创建选择面板控件
mPickerPanelView = new PickerPanelView(this);
// 将选择面板控件添加到 Activity 中
FrameLayout.LayoutParams layoutParams =
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
addContentView(mPickerPanelView, layoutParams);
4. 实现手势滑动
在选择面板控件中,我们需要实现手势滑动的效果。在 Android 中,我们可以通过实现 onTouchEvent()
方法来处理用户的触摸事件。
下面是 onTouchEvent()
方法的具体实现:
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mLastTouchY = event.getY();
mDownTime = System.currentTimeMillis();
mScroller.forceFinished(true);
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
float curTouchY = event.getY();
float deltaY = curTouchY - mLastTouchY;
mLastTouchY = curTouchY;
// 判断边界
if (getScrollY() + getHeight() >= mContentHeight
&& deltaY < 0) {
// 达到底部边界,禁止向上滑动
return true;
} else if (getScrollY() <= 0 && deltaY > 0) {
// 达到顶部边界,禁止向下滑动
return true;
} else {
// 进行滑动,调用 Scroller 相关方法
scrollBy(0, (int) -deltaY);
invalidate();
}
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
// 触摸事件结束,处理滑动结束后控件的位置
final int destY = getDestY();
mScroller.startScroll(getScrollX(), getScrollY(), 0, destY - getScrollY());
postInvalidateOnAnimation();
return true;
}
return super.onTouchEvent(event);
}
在上面的代码中,我们涉及了 Scroller 相关的方法,不过本文将不再详细介绍。我们注重讲解的是 onTouchEvent()
方法的具体实现。在该方法中,我们可以获取到用户手指的位置,并对位置进行处理,来实现手势滑动的效果。具体步骤如下:
- 在 ACTION_DOWN 事件中,获取用户手指的位置,并记录下当前时间以及位置。
- 在 ACTION_MOVE 事件中,计算出当前手指位置和上一次手指位置的差,然后将其传递给 Scroller 的 scrollBy() 方法,让 Scroller 处理手势滑动效果。
- 在 ACTION_UP 事件中,调用 Scroller 的 startScroll() 方法,让 Scroller 处理滑动结束后控件的位置。
至此,我们已经完成了手势滑动的自定义选择面板控件制作。具体参考下面的示例代码:
// 处理滑动结束后控件的位置
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
// 这里调用 View 的 scrollTo() 方法不会触发 View 的重绘
scrollTo(0, mScroller.getCurrY());
postInvalidateOnAnimation();
}
}
private int getDestY() {
// 根据速度计算出滑动的距离
final int MAX_VELOCITY = 10000;
final int MIN_VELOCITY = 50;
int duration = (int) (System.currentTimeMillis() - mDownTime);
int velocity = Math.round(Math.abs(getScrollY()) / (float) duration * 1000);
velocity = Math.min(velocity, MAX_VELOCITY);
velocity = Math.max(velocity, MIN_VELOCITY);
int distance;
if (velocity < MIN_VELOCITY) {
distance = (int) (getScrollY() - Math.round(getScrollY() / (float) ITEM_HEIGHT) * ITEM_HEIGHT);
} else if (velocity < MAX_VELOCITY) {
distance = (int) (getScrollY() - Math.round(getScrollY() / (float) ITEM_HEIGHT) * ITEM_HEIGHT);
distance = Math.round(distance / (float) ITEM_HEIGHT) * ITEM_HEIGHT;
} else {
distance = (int) Math.round(Math.abs(getScrollY()) / (float) ITEM_HEIGHT) * ITEM_HEIGHT - Math.abs(getScrollY()) % ITEM_HEIGHT;
if (getScrollY() < 0) {
distance = -distance;
}
}
return Math.min(distance, mContentHeight - getHeight() - getScrollY());
}
在上面的代码中,我们介绍了 Scroller 中的 computeScroll() 方法、scrollTo() 方法以及 startScroll() 方法。在 computeScroll() 方法中,我们不断地调用 Scroller 的 computeScrollOffset() 方法来计算滑动经过的位置,并将滑动的位置传递给 View 的 scrollTo() 方法。而在 getDestY() 方法中,我们计算出滑动结束后控件的位置,并将结果返回。
5. 示例说明
下面包含两个选择面板控件的示例,分别是单列和多列选择面板。这两个控件的区别在于:
- 单列选择面板控件中,只有一个垂直的列,用户只能在该列中选择一个项目;
- 多列选择面板控件中,有多个垂直的列,用户可以在每一列中分别选择一个项目。
下面是单列选择面板控件的示例描述:
5.1 单列选择面板控件示例
在本示例中,我们将实现一个单列选择面板控件,并使用该控件,在一个 TextView 中显示出用户选择的项目。
具体的实现步骤如下:
- 定义一个 Item 类,用于存储选择面板中的项目信息。
public class Item {
String text;
int value;
public Item(String text, int value) {
this.text = text;
this.value = value;
}
}
- 定义一个单列选择面板控件 SingleColumnPickerPanelView。
public class SingleColumnPickerPanelView extends PickerPanelView {
private List<Item> mDataList = new ArrayList<>();
private OnSelectedListener mListener;
public SingleColumnPickerPanelView(Context context) {
super(context);
}
public SingleColumnPickerPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SingleColumnPickerPanelView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setData(List<Item> data) {
mDataList = data;
for (int i = 0; i < mDataList.size(); i++) {
TextView textView = new TextView(getContext());
textView.setText(mDataList.get(i).text);
textView.setTextColor(Color.BLACK);
textView.setTextSize(18);
textView.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ITEM_HEIGHT);
layoutParams.gravity = Gravity.CENTER;
addView(textView, layoutParams);
}
mContentHeight = mDataList.size() * ITEM_HEIGHT;
}
public void setOnSelectedListener(OnSelectedListener listener) {
mListener = listener;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
if (mListener != null) {
if (event.getAction() == MotionEvent.ACTION_UP) {
int index = (getScrollY() + ITEM_HEIGHT / 2) / ITEM_HEIGHT;
if (index >= 0 && index < mDataList.size()) {
mListener.onSelected(this, mDataList.get(index).value);
}
}
}
return result;
}
public interface OnSelectedListener {
public void onSelected(SingleColumnPickerPanelView view, int value);
}
}
在上面的代码中,我们继承了自定义选择面板控件 PickerPanelView,实现了一个单列选择面板控件 SingleColumnPickerPanelView,并重写了该控件的 3 个构造函数。
其中 setData() 方法和 setOnSelectedListener() 方法用于绑定选择面板的数据和监听选择事件的回调,这两个方法都是在外部调用的。
而 onTouchEvent() 方法则用于处理当用户手指离开屏幕后,用户所选择的项目以及其对应的数值,并通过 callback 的方式将这些信息返回。
在 SingleColumnPickerPanelView 中,我们通过 addView() 方法,将数据项逐一添加到选择面板控件中,从而创建出一个类似于 iOS 上的 Picker 可供用户选择。同时,我们也为控件计算了选择面板的高度 mContentHeight,这将在后面的示例中用到。
- 在 Activity 中使用 SingleColumnPickerPanelView。
// 创建选择面板控件
mSingleColumnPickerPanelView = new SingleColumnPickerPanelView(this);
// 为选择面板控件绑定数据
List<Item> dataList = getDataList();
if (dataList != null && dataList.size() > 0) {
mSingleColumnPickerPanelView.setData(dataList);
}
// 为选择面板控件设置监听器
mSingleColumnPickerPanelView.setOnSelectedListener(this);
// 将选择面板控件添加到 Activity 中
FrameLayout.LayoutParams layoutParams =
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
addContentView(mSingleColumnPickerPanelView, layoutParams);
在上面的代码中,我们创建了一个 SingleColumnPickerPanelView 的实例,并为其绑定了数据和监听器。其中,getDataList() 方法用于获取选择面板中所包含的项目信息列表;mOnSingleColumnPickListener 用于监听选择面板的选择事件。
5.2 多列选择面板控件示例
在本示例中,我们将实现一个多列选择面板控件,并使用该控件,在一个 TextView 中显示出用户选择的项目。
具体的实现步骤如下:
- 定义一个 Model 类,用于存储选择面板中的项目信息。
public class Model<T> {
public String name;
public List<T> children;
}
- 定义一个包含多个列的选择面板控件 MultiColumnPickerPanelView。
```java
public class MultiColumnPickerPanelView
private List
private OnSelectedListener mListener;
public MultiColumnPickerPanelView(Context context) {
super(context);
}
public MultiColumnPickerPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MultiColumnPickerPanelView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setData(List<Model<T>> data) {
mDataList = data;
for (int i = 0; i < mDataList.size(); i++) {
LinearLayout linearLayout = new LinearLayout(getContext());
linearLayout.setOrientation(LinearLayout.VERTICAL);
linearLayout.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1));
for (int j = 0; j < mDataList.get(i).children.size(); j++) {
TextView textView = new TextView(getContext());
textView.setText(mDataList.get(i).children.get(j).toString());
textView.setTextColor(Color.BLACK);
textView.setTextSize(18);
textView.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ITEM_HEIGHT);
layoutParams.gravity = Gravity.CENTER;
linearLayout.addView(textView, layoutParams);
}
addView(linearLayout);
}
mContentHeight = mDataList.get(0).children.size() * ITEM_HEIGHT;
}
public void setOnSelectedListener(OnSelectedListener listener) {
mListener = listener;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
if (mListener != null) {
if (event.getAction() == MotionEvent.ACTION_UP) {
int row = (getScrollY() + ITEM_HEIGHT / 2) / ITEM_HEIGHT;
int column = getScrollX() / getWidth();
if (row >= 0 && row < mDataList.get(column).children.size()
&& column >= 0 && column < getChildCount()) {
mListener.onSelected(this, row, column,
mDataList.get(column).children.get(row));
}
}
}
return result;
}
public interface OnSelectedListener<T> {
public void onSelected(MultiColumnPickerPanelView view,
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Android自定义ViewGroup实现选择面板 - Python技术站