导言:
Android系统内置的View无法实现我们的需求,我们需要针对我们的业务需求定制我们想要的View,Viewgroup,属性,
并附上相关几个自定义实例供参考
1:自定义view和属性,温故而知新
//res/values/styles.xml文件
<resources>
<!--name为声明的"属性集合"名,可以随便取,但是最好是设置为跟我们的View一样的名称-->
<declare-styleable name="MyView">
<!--声明我们的属性,名称为default_size,取值类型为尺寸类型(dp,px等)-->
<attr name="default_size" format="dimension" />
</declare-styleable>
</resources>
//在布局文件使用自定义属性
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:hc="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.hc.studyview.MyView
android:layout_width="match_parent"
android:layout_height="100dp"
hc:default_size="100dp" />
</LinearLayout>
//自定义view完整代码
public class MyView extends View {
private int defalutSize;
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
//第二个参数就是我们在styles.xml文件中的<declare-styleable>标签
//即属性集合的标签,在R文件中名称为R.styleable+name
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView);
//第一个参数为属性集合里面的属性,R文件名称:R.styleable+属性集合名称+下划线+属性名称
//第二个参数为,如果没有设置这个属性,则设置的默认的值
defalutSize = a.getDimensionPixelSize(R.styleable.MyView_default_size, 100);
//最后记得将TypedArray对象回收
a.recycle();
}
private int getMySize(int defaultSize, int measureSpec) {
int mySize = defaultSize;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode) {
case MeasureSpec.UNSPECIFIED: {//如果没有指定大小,就设置为默认大小
mySize = defaultSize;
break;
}
case MeasureSpec.AT_MOST: {//如果测量模式是最大取值为size
//我们将大小取最大值,你也可以取其他值
mySize = size;
break;
}
case MeasureSpec.EXACTLY: {//如果是固定的大小,那就不要去改变它
mySize = size;
break;
}
}
return mySize;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMySize(defalutSize, widthMeasureSpec);
int height = getMySize(defalutSize, heightMeasureSpec);
if (width < height) {
height = width;
} else {
width = height;
}
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
//调用父View的onDraw函数,因为View这个类帮我们实现了一些
// 基本的而绘制功能,比如绘制背景颜色、背景图片等
super.onDraw(canvas);
int r = getMeasuredWidth() / 2;//也可以是getMeasuredHeight()/2,本例中我们已经将宽高设置相等了
//圆心的横坐标为当前的View的左边起始位置+半径
int centerX = getLeft() + r;
//圆心的纵坐标为当前的View的顶部起始位置+半径
int centerY = getTop() + r;
Paint paint = new Paint();
paint.setColor(Color.GREEN);
//开始绘制
canvas.drawCircle(centerX, centerY, r, paint);
}
}
2:自定义viewgroup(容器),温故而知新
//布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.hc.studyview.MyViewGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff9900">
<Button
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="btn" />
<Button
android:layout_width="200dp"
android:layout_height="wrap_content"
android:text="btn" />
<Button
android:layout_width="50dp"
android:layout_height="wrap_content"
android:text="btn" />
</com.hc.studyview.MyViewGroup>
</LinearLayout>
//MyViewGroup完整代码
public class MyViewGroup extends ViewGroup {
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
/***
* 获取子View中宽度最大的值
*/
private int getMaxChildWidth() {
int childCount = getChildCount();
int maxWidth = 0;
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
if (childView.getMeasuredWidth() > maxWidth)
maxWidth = childView.getMeasuredWidth();
}
return maxWidth;
}
/***
* 将所有子View的高度相加
**/
private int getTotleHeight() {
int childCount = getChildCount();
int height = 0;
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
height += childView.getMeasuredHeight();
}
return height;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//将所有的子View进行测量,这会触发每个子View的onMeasure函数
//注意要与measureChild区分,measureChild是对单个view进行测量
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childCount = getChildCount();
if (childCount == 0) {//如果没有子View,当前ViewGroup没有存在的意义,不用占用空间
setMeasuredDimension(0, 0);
} else {
//如果宽高都是包裹内容
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
//我们将高度设置为所有子View的高度相加,宽度设为子View中最大的宽度
int height = getTotleHeight();
int width = getMaxChildWidth();
setMeasuredDimension(width, height);
} else if (heightMode == MeasureSpec.AT_MOST) {//如果只有高度是包裹内容
//宽度设置为ViewGroup自己的测量宽度,高度设置为所有子View的高度总和
setMeasuredDimension(widthSize, getTotleHeight());
} else if (widthMode == MeasureSpec.AT_MOST) {//如果只有宽度是包裹内容
//宽度设置为子View中宽度最大的值,高度设置为ViewGroup自己的测量值
setMeasuredDimension(getMaxChildWidth(), heightSize);
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
//记录当前的高度位置
int curHeight = t;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
int height = child.getMeasuredHeight();
int width = child.getMeasuredWidth();
child.layout(l, curHeight, l + width, curHeight + height);
curHeight += height;
}
}
}
3:自定义RecyclerView分割线
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
private int mOrientation;
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
4:自定义ListView,适应滚动
public class MyListView extends ListView {
public MyListView(Context context) {
super(context);
}
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyListView(Context context, AttributeSet attrs,int defStyle) {
super(context, attrs, defStyle);
}
/**
*重写该方法,达到使ListView适应ScrollView的效果
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
5:自定义View,侧边字母定位
public class SideBar extends View {
// 触摸事件
private OnTouchingLetterChangedListener onTouchingLetterChangedListener;
public static String[] charArray;
private int choose = -1;
private Paint paint = new Paint();
private TextView mTextDialog;
/**
* 为SideBar设置显示字母的TextView
*/
public void setTextView(TextView textDialog) {
this.mTextDialog = textDialog;
}
public void setText(String[] charArray) {
this.charArray = charArray;
}
public SideBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public SideBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SideBar(Context context) {
super(context);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int height = getHeight();
int width = getWidth();
int singleHeight = height / charArray.length;// 获取每一个字母的高度
for (int i = 0; i < charArray.length; i++) {
paint.setColor(Color.rgb(33, 65, 98));
// paint.setColor(Color.WHITE);
paint.setTypeface(Typeface.DEFAULT_BOLD);
paint.setAntiAlias(true);
paint.setTextSize(30);
if (i == choose) {// 选中的状态
paint.setColor(Color.parseColor("#3399ff"));
paint.setFakeBoldText(true);
}
// x坐标等于中间-字符串宽度的一半
float xPos = width / 2 - paint.measureText(charArray[i]) / 2;
float yPos = singleHeight * i + singleHeight;
canvas.drawText(charArray[i], xPos, yPos, paint);
paint.reset();
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
final float y = event.getY();
final int oldChoose = choose;
final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;
final int c = (int) (y / getHeight() * charArray.length);// 点击y坐标所占总高度的比例*b数组的长度就等于点击b中的个数
switch (action) {
case MotionEvent.ACTION_UP:
setBackground(new ColorDrawable());
choose = -1;//
invalidate();
if (mTextDialog != null) {
mTextDialog.setVisibility(View.INVISIBLE);
}
break;
default:
setBackgroundResource(R.drawable.dialog_comite_shape);
if (oldChoose != c) {
if (c >= 0 && c < charArray.length) {
if (listener != null) {
listener.onTouchingLetterChanged(charArray[c]);
}
if (mTextDialog != null) {
mTextDialog.setText(charArray[c]);
mTextDialog.setVisibility(View.VISIBLE);
}
choose = c;
invalidate();
}
}
break;
}
return true;
}
/**
* 触摸事件
*/
public void setOnTouchingLetterChangedListener(
OnTouchingLetterChangedListener onTouchingLetterChangedListener) {
this.onTouchingLetterChangedListener = onTouchingLetterChangedListener;
}
public interface OnTouchingLetterChangedListener {
void onTouchingLetterChanged(String s);
}
}
6:自定义RelativeLayout,锁屏滑动
public class SildingFinishLayout extends RelativeLayout {
private final String TAG = SildingFinishLayout.class.getName();
private ViewGroup mParentView;
private int mTouchSlop;
private int downX;
private int downY;
private int tempX;
private Scroller mScroller;
private int viewWidth;
private boolean isSilding;
private OnSildingFinishListener onSildingFinishListener;
private boolean enableLeftSildeEvent = true;
private boolean enableRightSildeEvent = true;
private int size;
private boolean isIntercept = false;
private boolean canSwitch;
private boolean isSwitchFromLeft = false;
private boolean isSwitchFromRight = false;
public SildingFinishLayout(Context context) {
super(context);
init(context);
}
public SildingFinishLayout(Context context, AttributeSet attrs) {
super(context, attrs, 0);
init(context);
}
public SildingFinishLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mScroller = new Scroller(context);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
mParentView = (ViewGroup) this.getParent();
viewWidth = this.getWidth();
size = viewWidth;
}
}
public void setEnableLeftSildeEvent(boolean enableLeftSildeEvent) {
this.enableLeftSildeEvent = enableLeftSildeEvent;
}
public void setEnableRightSildeEvent(boolean enableRightSildeEvent) {
this.enableRightSildeEvent = enableRightSildeEvent;
}
public void setOnSildingFinishListener(
OnSildingFinishListener onSildingFinishListener) {
this.onSildingFinishListener = onSildingFinishListener;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
float downX = ev.getRawX();
Log.i(TAG, "downX =" + downX + ",viewWidth=" + viewWidth);
if (enableLeftSildeEvent && downX < size) {
Log.e(TAG, "downX 在左侧范围内 ,拦截事件");
isIntercept = true;
isSwitchFromLeft = true;
isSwitchFromRight = false;
return false;
} else if (enableRightSildeEvent && downX > (viewWidth - size)) {
isIntercept = true;
isSwitchFromRight = true;
isSwitchFromLeft = false;
return true;
} else {
isIntercept = false;
isSwitchFromLeft = false;
isSwitchFromRight = false;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isIntercept) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = tempX = (int) event.getRawX();
downY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) event.getRawX();
int deltaX = tempX - moveX;
tempX = moveX;
if (Math.abs(moveX - downX) > mTouchSlop && Math.abs((int) event.getRawY() - downY) < mTouchSlop) {
isSilding = true;
}
if (enableLeftSildeEvent) {//左侧滑动
if (moveX - downX >= 0 && isSilding) {
mParentView.scrollBy(deltaX, 0);
}
}
if (enableRightSildeEvent) {//右侧滑动
if (moveX - downX <= 0 && isSilding) {
mParentView.scrollBy(deltaX, 0);
}
}
break;
case MotionEvent.ACTION_UP:
isSilding = false;
if (mParentView.getScrollX() <= -viewWidth / 2 || mParentView.getScrollX() >= viewWidth / 2) {
canSwitch = true;
if (isSwitchFromLeft) {
scrollToRight();
}
if (isSwitchFromRight) {
scrollToLeft();
}
} else {
scrollOrigin();
canSwitch = false;
}
break;
}
return true;
}
private void scrollToRight() {
final int delta = (viewWidth + mParentView.getScrollX());
// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
mScroller.startScroll(mParentView.getScrollX(), 0, -delta + 1, 0, Math.abs(delta));
postInvalidate();
}
private void scrollToLeft() {
final int delta = (viewWidth - mParentView.getScrollX());
mScroller.startScroll(mParentView.getScrollX(), 0, delta - 1, 0, Math.abs(delta));//此处就不可用+1,也不卡直接用delta
postInvalidate();
}
private void scrollOrigin() {
int delta = mParentView.getScrollX();
mScroller.startScroll(mParentView.getScrollX(), 0, -delta, 0,
Math.abs(delta));
postInvalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
mParentView.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
if (mScroller.isFinished()) {
if (onSildingFinishListener != null && canSwitch) {
Log.i(TAG, "mScroller finish");
if (isSwitchFromLeft) {//回调,左侧切换事件
onSildingFinishListener.onSildingBack();
}
if (isSwitchFromRight) {//右侧切换事件
onSildingFinishListener.onSildingForward();
}
}
}
}
}
public interface OnSildingFinishListener {
public void onSildingBack();
public void onSildingForward();
}
}
7:自定义RecyclerView,滑动删除
public class SwipeRecyclerView extends RecyclerView {
private static final String TAG = "RecycleView";
private int maxLength, mTouchSlop;
private int xDown, yDown, xMove, yMove;
/**
* 当前选中的item索引(这个很重要)
*/
private int curSelectPosition;
private Scroller mScroller;
private LinearLayout mCurItemLayout, mLastItemLayout;
private Context mContext;
private TextView mItemContent;
/**
* 隐藏部分长度
*/
private int mHiddenWidth;
/**
* 记录连续移动的长度
*/
private int mMoveWidth = 0;
/**
* 是否是第一次touch
*/
private boolean isFirst = true;
private LinearLayout mLlHidden;//隐藏部分
private LinearLayout mItemDelete;
/**
* 删除的监听事件
*/
private OnRightClickListener mRightListener;
public void setRightClickListener(OnRightClickListener listener){
this.mRightListener = listener;
}
public SwipeRecyclerView(Context context) {
this(context, null);
}
public SwipeRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SwipeRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
//滑动到最小距离
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
//滑动的最大距离
maxLength = ((int) (180 * context.getResources().getDisplayMetrics().density + 0.5f));
//初始化Scroller
mScroller = new Scroller(context, new LinearInterpolator(context, null));
}
@Override
public boolean onTouchEvent(MotionEvent e) {
int x = (int)e.getX();
int y = (int)e.getY();
switch (e.getAction()){
case MotionEvent.ACTION_DOWN:
//记录当前按下的坐标
xDown = x;
yDown = y;
//计算选中哪个Item
int firstPosition = ((LinearLayoutManager)getLayoutManager()).findFirstVisibleItemPosition();
Rect itemRect = new Rect();
final int count = getChildCount();
for (int i=0; i<count; i++){
final View child = getChildAt(i);
if (child.getVisibility() == View.VISIBLE){
child.getHitRect(itemRect);
if (itemRect.contains(x, y)){
curSelectPosition = firstPosition + i;
break;
}
}
}
if (isFirst){//第一次时,不用重置上一次的Item
isFirst = false;
}else {
//屏幕再次接收到点击时,恢复上一次Item的状态
if (mLastItemLayout != null && mMoveWidth > 0) {
//将Item右移,恢复原位
scrollRight(mLastItemLayout, (0 - mMoveWidth));
//清空变量
mHiddenWidth = 0;
mMoveWidth = 0;
}
}
//取到当前选中的Item,赋给mCurItemLayout,以便对其进行左移
View item = getChildAt(curSelectPosition - firstPosition);
if (item != null) {
//获取当前选中的Item
// MyAdapter.ViewHolder viewHolder = (MyAdapter.ViewHolder) getChildViewHolder(item);
MerchTypeAdapter.ViewHolder viewHolder = (MerchTypeAdapter.ViewHolder) getChildViewHolder(item);
mCurItemLayout = viewHolder.ll_item;
mLlHidden = (LinearLayout)mCurItemLayout.findViewById(R.id.ll_hidden);
mItemDelete = (LinearLayout)mCurItemLayout.findViewById(R.id.ll_hidden);
mItemDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mRightListener != null){
//删除
mRightListener.onRightClick(curSelectPosition, "");
}
}
});
//这里将删除按钮的宽度设为可以移动的距离
mHiddenWidth = mLlHidden.getWidth();
}
break;
case MotionEvent.ACTION_MOVE:
xMove = x;
yMove = y;
int dx = xMove - xDown;//为负时:手指向左滑动;为正时:手指向右滑动。这与Android的屏幕坐标定义有关
int dy = yMove - yDown;//
//左滑
if (dx < 0 && Math.abs(dx) > mTouchSlop && Math.abs(dy) < mTouchSlop){
int newScrollX = Math.abs(dx);
if (mMoveWidth >= mHiddenWidth){//超过了,不能再移动了
newScrollX = 0;
} else if (mMoveWidth + newScrollX > mHiddenWidth){//这次要超了,
newScrollX = mHiddenWidth - mMoveWidth;
}
//左滑,每次滑动手指移动的距离
scrollLeft(mCurItemLayout, newScrollX);
//对移动的距离叠加
mMoveWidth = mMoveWidth + newScrollX;
}else if (dx > 0){//右滑
//执行右滑,这里没有做跟随,瞬间恢复
scrollRight(mCurItemLayout, 0 - mMoveWidth);
mMoveWidth = 0;
}
break;
case MotionEvent.ACTION_UP://手抬起时
int scrollX = mCurItemLayout.getScrollX();
if (mHiddenWidth > mMoveWidth) {
int toX = (mHiddenWidth - mMoveWidth);
if (scrollX > mHiddenWidth / 2) {//超过一半长度时松开,则自动滑到左侧
scrollLeft(mCurItemLayout, toX);
mMoveWidth = mHiddenWidth;
} else {//不到一半时松开,则恢复原状
scrollRight(mCurItemLayout, 0 - mMoveWidth);
mMoveWidth = 0;
}
}
mLastItemLayout = mCurItemLayout;
break;
}
return super.onTouchEvent(e);
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
Log.e(TAG, "computeScroll getCurrX ->" + mScroller.getCurrX());
mCurItemLayout.scrollBy(mScroller.getCurrX(), 0);
invalidate();
}
}
/**
* 向左滑动
*/
private void scrollLeft(View item, int scorllX){
Log.e(TAG, " scroll left -> " + scorllX);
item.scrollBy(scorllX, 0);
}
/**
* 向右滑动
*/
private void scrollRight(View item, int scorllX){
Log.e(TAG, " scroll right -> " + scorllX);
item.scrollBy(scorllX, 0);
}
public interface OnRightClickListener{
void onRightClick(int position, String id);
}
}
本文只是记录,可直接运行,若有疑问,请联系!

本文介绍了如何在Android中自定义视图组件以满足特定需求,包括View、ViewGroup及特殊功能视图如RecyclerView的分割线、侧边字母定位条等,并提供了多个实用示例。
1717

被折叠的 条评论
为什么被折叠?



