请尊重原创,转载请注明出处:http://blog.csdn.net/mabeijianxi/article/details/50560361
最近抽了些时间找了些资料,做了一个相对成熟的类似QQ小红点的拖拽控件。
先看下最后的效果:
![](https://img-blog.csdn.net/20160122105912818?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
simple与lib下载地址:https://github.com/mabeijianxi/stickyDots
一、分析:
1、首先分析这个控件的组成部分:
通过观察可以很明显的得出这个控件由三部分组成,一个固定不动的圆,一个连接部分,一个可能是圆的拖拽部分,由于不确定暂时把它看作圆
![](https://img-blog.csdn.net/20160122112148037?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
2、分析三个部分需要如何绘制。
(1)两个圆:这个比较简单,直接在复写view的onDraw方法,在里面执行canvas.drawCircle(),当然还需要传入圆心坐标和半径大小。
(2)连接部分:这个用过ps的矢量工具的应该知道。这里的是两条二阶贝塞尔曲线加两条直线。如图,二阶贝塞尔曲线是由起始点(P0,P2)和一个控制点(P1)组成。
![](https://img-blog.csdn.net/20160122200228776?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
二阶贝塞尔曲线在Android中的绘制方法可以调用Path类:
Path mPath=new Path();
mPath.moveTo(P0.x,P0.y);
mPath.quadTo(P1..x, P1.y, P2.x, P2.y);
直线就比较简单了:
mPath.lineTo(L0.x,L0.y);
(3)如何把每个部分结合起来,并且绘制在屏幕上:
有一个圆的圆心是固定的,可以先绘制。完成以后需要绘制连接部分,这个连接部分有两条曲线,两条直线,所以需要5个点才能绘制出来,
其中1个贝塞尔曲线的控制点,因为对称,所以控制点两条曲线公用一个控制点,。至于剩下的四个点,这里选取两个圆的外切点,如图:
![](https://img-blog.csdn.net/20160122204534012?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
接下来就是计算了,首先拖拽圆的圆心、半径可以知道,圆心坐标就是你手指触摸的位置,可以重写onTouchEvent()得到,固定圆圆心是不会变的,至于半径暂时给个确定值。
第一步:计算外切点:
根据两圆心所连接成的执行计算斜率:
公式是k=dy/dy;
dy=O1.y-O2.y;
dx=O1.x-O2.x;
有了斜率、半径与圆心计算切点就没有问题了,都是三角函数的一些换算,就不多说,具体的可以下载或者查看这个工具类GeometryUtil的计算过程。
第二步:计算控制点,其实就是O1与O2的中心点,x=(O1.x+O2.x)/2 y=(O1.y+O2.y)/2
第三步:根据计算出来的五个点开始绘制闭合图形
![](https://img-blog.csdn.net/20160122210538269?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
这里我选择B点开始绘制:
移动到B点:mPath.moveTo(B.x,B.y)
从B点向A点作二阶贝塞尔曲线:mPath.quadTo(M.x,M.y,A.x,A.y)
从A向C绘制直线:mPath.lineTo(C.x,C.y)
从C向D绘制二阶贝塞尔曲线:mPath.quadTo(M.x,M.y,D.x,D.y)
直接封闭图形就行了:mPath.close()
二、静态图像绘制:
下面的静态绘制的代码:
- package com.mabeijianxi.myapplication;
-
- import android.content.Context;
- import android.graphics.Canvas;
- import android.graphics.Color;
- import android.graphics.Paint;
- import android.graphics.Path;
- import android.graphics.PointF;
- import android.graphics.Rect;
- import android.view.View;
-
-
-
-
- public class StickyView extends View {
-
-
-
- PointF mDragCanterPoint = new PointF(250, 450);
-
-
-
- PointF mFixCanterPoint = new PointF(250, 250);
-
-
-
- PointF mCanterPoint = new PointF(250, 400);
-
-
-
-
- PointF[] mFixTangentPointes = new PointF[] { new PointF(235, 250),
- new PointF(265, 250) };
-
-
-
- PointF[] mDragTangentPoint = new PointF[] { new PointF(230, 450),
- new PointF(270, 450) };
-
-
-
- float mDragRadius = 20;
-
-
-
- float mFixRadius = 15;
- private int statusBarHeight;
- private Paint mPaint;
- private Path mPath;
-
- public StickyView(Context context) {
- super(context);
- mPaint = new Paint();
- mPaint.setColor(Color.RED);
- mPaint.setAntiAlias(true);
- mPath = new Path();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- canvas.save();
- canvas.translate(0, -statusBarHeight);
- canvas.drawCircle(mFixCanterPoint.x, mFixCanterPoint.y, mFixRadius,
- mPaint);
-
- float dy = mDragCanterPoint.y - mFixCanterPoint.y;
- float dx = mDragCanterPoint.x - mFixCanterPoint.x;
-
- mCanterPoint.set((mDragCanterPoint.x + mFixCanterPoint.x) / 2,
- (mDragCanterPoint.y + mFixCanterPoint.y) / 2);
-
- if (dx != 0) {
- float k1 = dy / dx;
- float k2 = -1 / k1;
- mDragTangentPoint = getIntersectionPoints(
- mDragCanterPoint, mDragRadius, (double) k2);
- mFixTangentPointes = getIntersectionPoints(
- mFixCanterPoint, mFixRadius, (double) k2);
- } else {
- mDragTangentPoint = getIntersectionPoints(
- mDragCanterPoint, mDragRadius, (double) 0);
- mFixTangentPointes = getIntersectionPoints(
- mFixCanterPoint, mFixRadius, (double) 0);
- }
-
- mPath.reset();
- mPath.moveTo(mFixTangentPointes[0].x, mFixTangentPointes[0].y);
- mPath.quadTo(mCanterPoint.x, mCanterPoint.y,
- mDragTangentPoint[0].x, mDragTangentPoint[0].y);
- mPath.lineTo(mDragTangentPoint[1].x, mDragTangentPoint[1].y);
- mPath.quadTo(mCanterPoint.x, mCanterPoint.y,
- mFixTangentPointes[1].x, mFixTangentPointes[1].y);
- mPath.close();
- canvas.drawPath(mPath, mPaint);
-
- canvas.drawCircle(mDragCanterPoint.x, mDragCanterPoint.y,
- mDragRadius, mPaint);
-
- canvas.restore();
- }
-
-
-
-
-
- public static int getStatusBarHeight(View v) {
- if (v == null) {
- return 0;
- }
- Rect frame = new Rect();
- v.getWindowVisibleDisplayFrame(frame);
- return frame.top;
- }
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- statusBarHeight=getStatusBarHeight(this);
- }
-
-
-
-
-
-
-
-
-
-
- public static PointF[] getIntersectionPoints(PointF pMiddle, float radius, Double lineK) {
- PointF[] points = new PointF[2];
-
- float radian, xOffset = 0, yOffset = 0;
- if(lineK != null){
-
- radian= (float) Math.atan(lineK);
- xOffset = (float) (Math.cos(radian) * radius);
- yOffset = (float) (Math.sin(radian) * radius);
- }else {
- xOffset = radius;
- yOffset = 0;
- }
- points[0] = new PointF(pMiddle.x + xOffset, pMiddle.y + yOffset);
- points[1] = new PointF(pMiddle.x - xOffset, pMiddle.y - yOffset);
-
- return points;
- }
- }
Activity:
- package com.mabeijianxi.myapplication;
-
- import android.app.Activity;
- import android.os.Bundle;
-
- public class MainActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(new StickyView(this));
- }
- }
运行结果:
![](https://img-blog.csdn.net/20160125110526142?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
是不是觉得挺简单的,下面开始动态绘制。
三、动态图形绘制:
有了绘制静态图的经验,绘制动态图将变得很简单,无非就是根据手指触摸的位置计算拖拽圆的圆心坐标、控制点的坐标与控制点的坐标,很明显
拖拽圆的坐标就是我们手指触摸的位置,固定圆坐标不变,那么其他的坐标的计算方法我们已经在二中已经知道了,比如控制点就是两圆心的中点。
切点可根据三角函数求出。公式全都有了,现在只需要动态绘制就可以了。
获取手指触摸的坐标,大家应该都会,直接上代码了:
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
-
- float startX = event.getRawX();
- float startY = event.getRawY();
- updateDragCenterPoint(startX, startY);
- break;
- case MotionEvent.ACTION_MOVE:
- float endX = event.getRawX();
- float endY = event.getRawY();
-
- updateDragCenterPoint(endX, endY);
-
- break;
- }
- return true;
- }
-
-
-
- private void updateDragCenterPoint(float x, float y) {
- mDragCanterPoint.set(x, y);
- invalidate();
- }
运行示意图:
![](https://img-blog.csdn.net/20160125113535463?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
四、根据业务增加功能与动画。
1、观察分析:
a.固定圆虽然圆心不变,但是半径在变化,并且与两圆的圆心距成正比。
b.拖拽过程中有一个范围。
c.拖拽过程中根据拖拽范围有几个状态:①只在范围内移动,正常全部绘制。
②只范围内移动,最后在范围内松手,作回弹动画。
③范围外移动过,只要超出过一次将不再绘制固定圆与连接杆。
④
范围外移动过,最后在范围外松手,直接让所有图像消失。
⑤范围外移动过,最后在范围内松手,图形重设到初始状态。
2、实现:
a.固定圆半径动态变化,这个可以更具拖拽圆与固定圆之间的距离来变化:
-
-
-
- private float updateStickRadius() {
- float distance = (float) Math.sqrt(Math.pow(mDragCanterPoint.y - mFixCanterPoint.y, 2)
- + Math.pow(mDragCanterPoint.x - mFixCanterPoint.x, 2));
- distance = Math.min(distance, mFarthestDistance);
- float percent = distance * 1.0f / mFarthestDistance;
- return mFixRadius + (mMinFixRadius - mFixRadius) * percent;
- }
b. 配置回弹动画,通过值动画来模拟数据,其实就是以拖拽圆为起点固定圆为终点不停的直线移动拖拽圆的坐标:
-
-
-
- private void inUp() {
- final PointF startPoint = new PointF(mDragCanterPoint.x,
- mDragCanterPoint.y);
- final PointF endPoint = new PointF(mFixCanterPoint.x,
- mFixCanterPoint.y);
- ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float fraction = animation.getAnimatedFraction();
- PointF byPercent = GeometryUtil.getPointByPercent(
- startPoint, endPoint, fraction);
- updateDragCenterPoint(byPercent.x, byPercent.y);
- }
- });
- animator.setInterpolator(new OvershootInterpolator(4.0f));
- animator.setDuration(500);
- animator.start();
- }
c. 增加状态标识,这里只需要两个就够了,一个代表是否超出过范围,一个代表现在是在范围外还是范围内。
-
-
-
- private boolean isOut;
-
-
-
- private boolean isOutUp;
只需要在触摸与绘制过程中动态改变或者判断它们就可以了。
以上分析得出code:
- package com.mabeijianxi.myapplication;
-
- import android.animation.ValueAnimator;
- import android.content.Context;
- import android.graphics.Canvas;
- import android.graphics.Color;
- import android.graphics.Paint;
- import android.graphics.Path;
- import android.graphics.PointF;
- import android.graphics.Rect;
- import android.view.MotionEvent;
- import android.view.View;
- import android.view.animation.OvershootInterpolator;
-
-
-
-
- public class StickyView extends View {
-
-
-
- private float mFarthestDistance = 200;
-
-
-
- private float mMinFixRadius = 8;
-
-
-
- PointF mDragCanterPoint = new PointF(250, 250);
-
-
-
- PointF mFixCanterPoint = new PointF(250, 250);
-
-
-
- PointF mCanterPoint = new PointF(250, 250);
-
-
-
-
- PointF[] mFixTangentPointes = new PointF[]{new PointF(235, 250),
- new PointF(265, 250)};
-
-
-
- PointF[] mDragTangentPoint = new PointF[]{new PointF(230, 250),
- new PointF(270, 250)};
-
-
-
- float mDragRadius = 20;
-
-
-
- float mFixRadius = 15;
-
-
-
- private boolean isOut;
-
-
-
- private boolean isOutUp;
- private int mStatusBarHeight;
- private Paint mPaint;
- private Path mPath;
- private float rangeMove;
-
- public StickyView(Context context) {
- super(context);
- mPaint = new Paint();
- mPaint.setColor(Color.RED);
- mPaint.setAntiAlias(true);
- mPath = new Path();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- canvas.save();
-
- canvas.translate(0, -mStatusBarHeight);
-
- if (!isOut) {
-
- float mFixRadius = updateStickRadius();
- canvas.drawCircle(mFixCanterPoint.x, mFixCanterPoint.y, mFixRadius,
- mPaint);
-
- mCanterPoint.set((mDragCanterPoint.x + mFixCanterPoint.x) / 2,
- (mDragCanterPoint.y + mFixCanterPoint.y) / 2);
-
- float dy = mDragCanterPoint.y - mFixCanterPoint.y;
- float dx = mDragCanterPoint.x - mFixCanterPoint.x;
-
- if (dx != 0) {
- float k1 = dy / dx;
- float k2 = -1 / k1;
- mDragTangentPoint = getIntersectionPoints(
- mDragCanterPoint, mDragRadius, (double) k2);
- mFixTangentPointes = getIntersectionPoints(
- mFixCanterPoint, mFixRadius, (double) k2);
- } else {
- mDragTangentPoint = getIntersectionPoints(
- mDragCanterPoint, mDragRadius, (double) 0);
- mFixTangentPointes = getIntersectionPoints(
- mFixCanterPoint, mFixRadius, (double) 0);
- }
-
- mPath.reset();
-
- mPath.moveTo(mFixTangentPointes[0].x, mFixTangentPointes[0].y);
-
- mPath.quadTo(mCanterPoint.x, mCanterPoint.y,
- mDragTangentPoint[0].x, mDragTangentPoint[0].y);
-
- mPath.lineTo(mDragTangentPoint[1].x, mDragTangentPoint[1].y);
-
- mPath.quadTo(mCanterPoint.x, mCanterPoint.y,
- mFixTangentPointes[1].x, mFixTangentPointes[1].y);
-
- mPath.close();
-
- canvas.drawPath(mPath, mPaint);
-
- }
-
- if (!isOutUp) {
- canvas.drawCircle(mDragCanterPoint.x, mDragCanterPoint.y,
- mDragRadius, mPaint);
- }
-
- mPaint.setStyle(Paint.Style.STROKE);
- canvas.drawCircle(mFixCanterPoint.x, mFixCanterPoint.y, mFarthestDistance, mPaint);
- mPaint.setStyle(Paint.Style.FILL);
-
- canvas.restore();
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- isOut = false;
- float startX = event.getRawX();
- float startY = event.getRawY();
- updateDragCenterPoint(startX, startY);
- break;
- case MotionEvent.ACTION_MOVE:
- float endX = event.getRawX();
- float endY = event.getRawY();
-
- updateDragCenterPoint(endX, endY);
- distance();
-
- if (rangeMove > mFarthestDistance) {
- isOut = true;
- } else {
-
-
- isOutUp = false;
- }
- break;
- case MotionEvent.ACTION_UP:
-
- distance();
- if (isOut) {
- outUp();
- }
-
- else {
- inUp();
- }
- invalidate();
- break;
- }
- return true;
- }
-
-
-
- private void outUp() {
-
- if (rangeMove > mFarthestDistance) {
- isOutUp = true;
- }
-
- else {
- isOutUp = false;
- }
- updateDragCenterPoint(mFixCanterPoint.x, mFixCanterPoint.y);
- }
-
-
-
- private void distance() {
- rangeMove = getDistanceBetween2Points(
- mFixCanterPoint, mDragCanterPoint);
- }
-
-
-
-
- private void inUp() {
- final PointF startPoint = new PointF(mDragCanterPoint.x,
- mDragCanterPoint.y);
- final PointF endPoint = new PointF(mFixCanterPoint.x,
- mFixCanterPoint.y);
- ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float fraction = animation.getAnimatedFraction();
- PointF byPercent = getPointByPercent(
- startPoint, endPoint, fraction);
- updateDragCenterPoint(byPercent.x, byPercent.y);
- }
- });
- animator.setInterpolator(new OvershootInterpolator(4.0f));
- animator.setDuration(500);
- animator.start();
- }
-
-
-
-
- private float updateStickRadius() {
- float distance = (float) Math.sqrt(Math.pow(mDragCanterPoint.y - mFixCanterPoint.y, 2)
- + Math.pow(mDragCanterPoint.x - mFixCanterPoint.x, 2));
- distance = Math.min(distance, mFarthestDistance);
- float percent = distance * 1.0f / mFarthestDistance;
- return mFixRadius + (mMinFixRadius - mFixRadius) * percent;
- }
-
-
-
-
- private void updateDragCenterPoint(float x, float y) {
- mDragCanterPoint.set(x, y);
- invalidate();
- }
-
-
-
-
-
-
-
- public static int getStatusBarHeight(View v) {
- if (v == null) {
- return 0;
- }
- Rect frame = new Rect();
- v.getWindowVisibleDisplayFrame(frame);
- return frame.top;
- }
-
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- mStatusBarHeight = getStatusBarHeight(this);
- }
-
-
-
-
-
-
-
-
- public static PointF getPointByPercent(PointF p1, PointF p2, float percent) {
- return new PointF(evaluateValue(percent, p1.x , p2.x), evaluateValue(percent, p1.y , p2.y));
- }
-
-
-
-
-
-
-
- public static float evaluateValue(float fraction, Number start, Number end){
- return start.floatValue() + (end.floatValue() - start.floatValue()) * fraction;
- }
-
-
-
-
-
-
-
- public static float getDistanceBetween2Points(PointF p0, PointF p1) {
- float distance = (float) Math.sqrt(Math.pow(p0.y - p1.y, 2) + Math.pow(p0.x - p1.x, 2));
- return distance;
- }
-
-
-
-
-
-
-
-
-
- public static PointF[] getIntersectionPoints(PointF pMiddle, float radius, Double lineK) {
- PointF[] points = new PointF[2];
-
- float radian, xOffset = 0, yOffset = 0;
- if (lineK != null) {
-
- radian = (float) Math.atan(lineK);
- xOffset = (float) (Math.cos(radian) * radius);
- yOffset = (float) (Math.sin(radian) * radius);
- } else {
- xOffset = radius;
- yOffset = 0;
- }
- points[0] = new PointF(pMiddle.x + xOffset, pMiddle.y + yOffset);
- points[1] = new PointF(pMiddle.x - xOffset, pMiddle.y - yOffset);
-
- return points;
- }
-
- }
Activity:
- package com.mabeijianxi.myapplication;
-
- import android.app.Activity;
- import android.os.Bundle;
-
- public class MainActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(new StickyView(this));
- }
- }
以上代码运行示意图:
![](https://img-blog.csdn.net/20160125151951285?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
五、添加到合适的项目中:
也许你觉得应该结束了,然而其实才刚刚开始,下面会涉及到WindowManager与event的一些操作。
1、分析:比如要像QQ一样添加到listview中应该怎么做?一个listview里面有很多条目,我们这个拖拽控件是在条目中的,也就是说如果要像一般控件一样
直接添加到条目中,那么他将不能做出这一系列的拖拽,因为只能在父控件的区域内绘制,也就只能在一个条目的范围内移动,那么怎么可以实现
全屏移动呢?除非它的parent大小充满整个屏幕,这时应该想到WindowManager。只要在需要的时候把它添加到WindowManager就可以了,那么
什么时候需要呢?很明显是当手指触摸到需要被拖拽的控件的时候,所以肯定需要给需要拖拽的控件设置触摸监听。那么问题又来了,我们写的自定
拖拽控件的拖拽部分是一个圆,我们需要被拖拽的控件可能是一坨狗屎也可能是一朵鲜花,怎么办呢?其实很简单,让我们需要拖拽的狗屎也添加到
WindowManager中,狗屎的中心坐标恒等于拖拽圆的圆心就可以。一个View如果需要加到一个父控件中,那么添加之前这个View坑定不能有parent,
没有哪个孩子是两个爹的,所以需要先view.getParent(),如果其parent instanceof viewGroup那么就可以remove掉这个view,当然remove之前还需要
先得到view的LayoutParams,因为你动画做完了还需要把这个view还给原来的listview的条目。这个方法是不错,但是在一个view被remove了以后再添加
回去,这时将会有些得不到,具体的代码里面有注释,其实就是得不到,view在屏幕上的绝对坐标,我能想到的就是这个view被移除后坑定也在这个当前
窗体里面被移除,将再添加回去的时候,可能没有再次绑定到窗体,所提得不到坐标,具体详解请参考如何取得View的位置之View.getLocationInWindow()的
小秘密,我尝试了很多次依然有bug,如果大家有解决办法一定要吝啬和我分享一下呗。于是我采取了另外一个办法,每隔需要被拖拽的狗屎在被触摸时将
被隐藏,然后会新建一个一模一样的狗屎用来做动画,这样的话不管新建的狗屎解决如何都不重要,当然这样对内容会有那么多一点点的消耗。
2、实践:这里添加了一个辅助类当作桥梁作用,请点击StickyViewHelper,具体操作方法请点击simple。
3、使用lib依赖说明:麻烦一点就是需要被拖拽的控件在布局文件中要单独指定,然后在需要使用的地方用include.像这样:
includeview:
- <?xml version="1.0" encoding="utf-8"?>
- <TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/mDragView"
- android:layout_width="wrap_content"
- android:layout_height="20dp"
- android:background="@drawable/red_bg"
- android:gravity="center"
- android:layout_gravity="center"
- android:singleLine="true"
- android:text="1"
- android:textSize="13sp"
- android:textColor="@android:color/white"
- />
item:
- <include
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_centerVertical="true"
- android:layout_alignParentRight="true"
- android:layout_marginRight="15dp"
- layout="@layout/includeview"
- />
具体的请参照sample,内附详细注释。
sample与lib下载地址:https://github.com/mabeijianxi/stickyDots