android自定义控件系列教程----继承ViewGroup实现带阻力效果的可回弹的SrollView

前沿分析:

我为什么要想实现一个这样的回弹呢?因为android都没有支持回弹效果,只有个oversroll的回弹效果,其他的时候都是edgeeffect效果,当我们在哪个地方需要这样的回弹效果我们就直接把我们的控件往这个SrollVIew里面一扔就可以了。其他的都不用管。

主要用到的类讲解:

Scroller,主要来辅助我们记录动画和滑动的类,VelocityTracker用来计算滑动阀值就是快速滑动的辅助类,用到的辅助类就这两个,其他的就是测量和布局还有事件的编写了。

效果图

里面的按钮是我真正的圆角button来了里面的按钮,可以看看,效果还是不错的,这里只是给大家提供了这样的一个思路大家以后下去可以自己改进。

实例代码讲解

首先开始我们还是先来理清一下我们的思路,定义好我们的类,先上我们类的定义吧。

class QSrollView extends ViewGroup {
    public final static String TAG = QSrollView.class.getSimpleName();
    public final static int TOUCH_STATE_SROLLING = 1; // 当前在滑动状态
    public final static int TOUCH_STATE_FLING = 2; // 当前fling状态
    public final static int TOUCH_STATE_DEFALUT = 0; // 默认

    private int mTouchState = TOUCH_STATE_DEFALUT;
    private int mTouchSlop = 0; // 当前滑动阀值

    private int mLastMontionY; // 记录上次y的位置

    Scroller mScroller; // 滑动辅助类

    private int mTotalLength = 0; // 整个控件的长度
    private int mMaxmumVelocity = 0; // Velocity的阀值
    private VelocityTracker mVelocityTracker; // Velocity

    int mPointID = 0; // pointID

    public QSrollView(Context context) {
	super(context);
	init();
    }

    private void init() {
	mScroller = new Scroller(getContext());
	mTouchSlop = ViewConfiguration.getTouchSlop();
	mMaxmumVelocity = ViewConfiguration.getMaximumFlingVelocity();
    }

显示定义我们的类和初始化我们的变量,这些都很简单,然后我们来写一下它的onMeasure方法

   /**
     * 重写onMeasure方法计算
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	final int size = getChildCount();
	final int parentWidthSize = MeasureSpec.getSize(widthMeasureSpec);
	final int paretnHeightSize = MeasureSpec.getSize(heightMeasureSpec);
	for (int i = 0; i < size; ++i) {
	    final View child = getChildAt(i);
	    if (child.getVisibility() != GONE) {
		LayoutParams childLp = child.getLayoutParams();
		final boolean childWidthWC = childLp.width == LayoutParams.WRAP_CONTENT;
		final boolean childHeightWC = childLp.height == LayoutParams.WRAP_CONTENT;
		int childWidthMeasureSpec;
		int childHeightMeasureSpec;
		if (child.getLayoutParams() instanceof MarginLayoutParams) {
		    MarginLayoutParams childMarginLp = (MarginLayoutParams) childLp;
		    childWidthMeasureSpec = childWidthWC ? MeasureSpec
			    .makeMeasureSpec(parentWidthSize,
				    MeasureSpec.UNSPECIFIED)
			    : getChildMeasureSpec(widthMeasureSpec,
				    getPaddingLeft() + getPaddingRight()
					    + childMarginLp.leftMargin
					    + childMarginLp.rightMargin,
				    childLp.width);
		    childHeightMeasureSpec = childHeightWC ? MeasureSpec
			    .makeMeasureSpec(paretnHeightSize,
				    MeasureSpec.UNSPECIFIED)
			    : getChildMeasureSpec(heightMeasureSpec,
				    getPaddingTop() + getPaddingBottom()
					    + childMarginLp.topMargin
					    + childMarginLp.bottomMargin,
				    childMarginLp.height);
		} else {
		    childWidthMeasureSpec = childWidthWC ? MeasureSpec
			    .makeMeasureSpec(parentWidthSize,
				    MeasureSpec.UNSPECIFIED)
			    : getChildMeasureSpec(widthMeasureSpec,
				    getPaddingLeft() + getPaddingRight(),
				    childLp.width);
		    childHeightMeasureSpec = childHeightWC ? MeasureSpec
			    .makeMeasureSpec(paretnHeightSize,
				    MeasureSpec.UNSPECIFIED)
			    : getChildMeasureSpec(heightMeasureSpec,
				    getPaddingTop() + getPaddingBottom(),
				    childLp.height);
		}
		child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
	    }
	}

	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

计算它每一个孩子的高度,这里这里我直接用的系统的MarginLayoutParams,可以实现我们控件的margin效果,还有就是我们写的这个Scrollview可以add多次child不像系统提供的ScrollVIew一样只能add一个孩子,最后记得一定要调用一下这个函数super.onMeasure(widthMeasureSpec, heightMeasureSpec);其实在底层是调用的setMeasuredDimension这个函数,它的作用是告诉控件自己测量完了大小是多少,然后接下来就是我们的OnLayout方法了。

 /***
     * 重写layout方法
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

	int childStartPostion = 0;
	mTotalLength = 0;
	final int count = getChildCount();
	if (count == 0) {
	    return;
	}
	childStartPostion = getPaddingTop();
	for (int i = 0; i < count; i++) {
	    final View child = getChildAt(i);
	    if (child != null && child.getVisibility() != View.GONE) {
		LayoutParams lp = child.getLayoutParams();
		final int childHeight = child.getMeasuredHeight();
		int leftMargin = 0;
		int rightMargin = 0;
		int topMargin = 0;
		int bottomMargin = 0;
		if (lp instanceof MarginLayoutParams) {
		    MarginLayoutParams mlp = (MarginLayoutParams) lp;
		    leftMargin = mlp.leftMargin;
		    rightMargin = mlp.rightMargin;
		    topMargin = mlp.topMargin;
		    bottomMargin = mlp.bottomMargin;
		}

		childStartPostion += topMargin;
		int startX = (getWidth() - leftMargin - rightMargin - child
			.getMeasuredWidth()) / 2 + leftMargin;
		child.layout(startX, childStartPostion,
			startX + child.getMeasuredWidth(), childStartPostion
				+ childHeight);
		childStartPostion += (childHeight + bottomMargin);
	    }
	}
	childStartPostion += getPaddingBottom();
	mTotalLength = childStartPostion;

    }

onlayout方法很简单但是要注意一点的是,我们是从上到下的的放置我们的控件的,就是我们的垂直布局。然后记录我们所有的控件的总共高度。如果这两部没有问题,那么我们的控件就应该可以跑起来了,可以当一个LiearLayout的垂直布局用了,但是没有居中这些属性。

过了就是我们的事件拦截的写法了。

  /**
     * 事件拦截
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
	final int action = ev.getAction();
	// 表示已经开始滑动了,不需要走该Action_MOVE方法了(第一次时可能调用)。
	if ((action == MotionEvent.ACTION_MOVE)
		&& (mTouchState != TOUCH_STATE_DEFALUT)) {
	    return true;
	}
	int y = (int) ev.getY();
	switch (action) {
	case MotionEvent.ACTION_MOVE:
	    final int xDiff = (int) Math.abs(mLastMontionY - y);
	    // 超过了最小滑动距离
	    if (xDiff > mTouchSlop) {
		mTouchState = TOUCH_STATE_SROLLING;
	    }
	    break;
	case MotionEvent.ACTION_POINTER_DOWN:
	    mPointID = ev.getPointerId(ev.getActionIndex()); // 记录当前pointID
	    break;
	case MotionEvent.ACTION_DOWN:
	    mLastMontionY = y;
	    Log.e(TAG, mScroller.isFinished() + "");
	    if (!mScroller.isFinished()) // 当动画还没有结束的时候强制结束
	    {
		mScroller.abortAnimation();
		mScroller.forceFinished(true);
	    }
	    mTouchState = TOUCH_STATE_DEFALUT;
	    break;

	case MotionEvent.ACTION_CANCEL:
	case MotionEvent.ACTION_UP:
	    mTouchState = TOUCH_STATE_DEFALUT;
	    break;
	}
	Log.e(TAG, mTouchState + "====" + TOUCH_STATE_DEFALUT);
	return mTouchState != TOUCH_STATE_DEFALUT;
    }

事件拦截也比较简单,就是在滑动超过我们的阀值的时候改变当前滑动的状态并拦截,不让它忘子控件里面分发事件了。需要注意的是在ACTION_POINTER_DOWN这个事件的时候我们需要记录它的PointId因为这样在后面的一个手指不放开,另外一个手指又上去滑动的时候才有效果。重点来了我们的onTouchEvent方法。

@Override
    public boolean onTouchEvent(MotionEvent event) {

	int touchIndex = event.getActionIndex();
	if (mVelocityTracker == null) {
	    mVelocityTracker = VelocityTracker.obtain();
	}
	mVelocityTracker.addMovement(event);

	switch (event.getAction()) {
	case MotionEvent.ACTION_DOWN:
	    mPointID = event.getPointerId(0);
	    mLastMontionY = (int) event.getY();// 记录按下的点
	    break;
	case MotionEvent.ACTION_POINTER_DOWN: // 添加多点触控的处理
	    mPointID = event.getPointerId(touchIndex);
	    mLastMontionY = (int) (event.getY(touchIndex) + 0.5f); // 记录按下的点
	    break;

	case MotionEvent.ACTION_MOVE:
	    touchIndex = event.findPointerIndex(mPointID);
	    if (touchIndex < 0) // 当前index小于0就返false继续接受下一次事件
		return false;
	    int detaY = (int) (mLastMontionY - event.getY(touchIndex)); // 计算滑动的距离
	    scrollBy(0, detaY); // 调用滑动函数
	    mLastMontionY = (int) event.getY(touchIndex); // 记录上一次按下的点
	    break;
	case MotionEvent.ACTION_UP:
	    Log.d("edsheng", "Action UP");
	    mVelocityTracker.computeCurrentVelocity(1000);
	    if (Math.abs(mVelocityTracker.getYVelocity()) > mMaxmumVelocity&&!checkIsBroad()) {
		mScroller.fling(getScrollX(), getScrollY(), 0,-(int) mVelocityTracker.getYVelocity(), 0, 0, 0,
			mTotalLength - getHeight());
	    } else {
		actionUP(); // 回弹效果
	    }

	    mTouchState = TOUCH_STATE_DEFALUT;

	    break;
	case MotionEvent.ACTION_POINTER_UP:
	    // 添加多点触控的支持
	    if (event.getPointerId(touchIndex) == mPointID) {
		final int newIndex = touchIndex == 0 ? 1 : 0;
		mPointID = event.getPointerId(newIndex);
		mLastMontionY = (int) (event.getY(newIndex) + 0.5f);
	    }
	    break;
	case MotionEvent.ACTION_CANCEL:
	    mTouchState = TOUCH_STATE_DEFALUT;
	    break;
	default:
	    break;
	}
	// super.onTouchEvent(event);
	return true;
    }

可以看到在touch方法里面主要是在ACTION_MOVE的时候调用我们的 scrollBy(0, detaY); 这个函数去滚动我们的视图,然后在ACTION_UP事件里面处理回弹还是fling效果。很重要的一点说好的阻力效果呢?我们在滑动的时候是不是调用了ScrollBy这个方法呢?每次都去滑动,然后我们就看看到底咋个来实现阻力效果的?

    @Override
    public void scrollBy(int x, int y) {
	// 判断当前视图是否超过了顶部或者顶部就让它滑动的距离为1/3这样就有越拉越拉不动的效果
	if (getScrollY() < 0 || getScrollY() + getHeight() > mTotalLength) {
	    super.scrollBy(x, y / 3);
	} else {
	    super.scrollBy(x, y);
	}
    }

看到了吗?当越界或者在底部的时候就让它滑动的时候为原来的1/3这样就有越来越滑不动的效果了,简单有效。然后就是我们的回弹了,看看我们的回弹吧。

  /**
     * 回弹函数
     */
    private void actionUP() {
	if (getScrollY() < 0 || getHeight() > mTotalLength) // 顶部回弹
	{
	    Log.d("edsheng", "顶部回弹!!!!");
	    mScroller.startScroll(0, getScrollY(), 0, -getScrollY()); // 开启回弹效果
	    invalidate();
	} else if (getScrollY() + getHeight() > mTotalLength) // 底部回弹
	{
	    // 开启底部回弹
	    mScroller.startScroll(0, getScrollY(), 0, -(getScrollY()
		    + getHeight() - mTotalLength));
	    invalidate();
	}
    }

这里就用到了我们Scroller去帮助我们计算动画,重要的一点是在调用了startScroll的时候一定要记得invalidate一下。还有就是当计算到动画的时候我们要重写一些这个computeScroll这个方法是系统在重绘的时候会自动调用这个方法。

  @Override
    public void computeScroll() {
	if (mScroller.computeScrollOffset()) // 计算当前位置
	{
	    // 滚动
	    scrollTo(0, mScroller.getCurrY());
	    postInvalidate();
	}
    }

最后贴上全部的类代码。

package com.example.scolview;

import android.content.Context;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.ListView;
import android.widget.ScrollView;
import android.widget.Scroller;

import java.io.InputStream;

/*
 * @FileName:QSrollView.java
 * @Version:V1.0
 * @Date: 2015-2-1 Create
 * @author: edsheng
 * */
class QSrollView extends ViewGroup {
    public final static String TAG = QSrollView.class.getSimpleName();
    public final static int TOUCH_STATE_SROLLING = 1; // 当前在滑动状态
    public final static int TOUCH_STATE_FLING = 2; // 当前fling状态
    public final static int TOUCH_STATE_DEFALUT = 0; // 默认

    private int mTouchState = TOUCH_STATE_DEFALUT;
    private int mTouchSlop = 0; // 当前滑动阀值

    private int mLastMontionY; // 记录上次y的位置

    Scroller mScroller; // 滑动辅助类

    private int mTotalLength = 0; // 整个控件的长度
    private int mMaxmumVelocity = 0; // Velocity的阀值
    private VelocityTracker mVelocityTracker; // Velocity

    int mPointID = 0; // pointID

    public QSrollView(Context context) {
	super(context);
	init();
    }

    private void init() {
	mScroller = new Scroller(getContext());
	mTouchSlop = ViewConfiguration.getTouchSlop();
	mMaxmumVelocity = ViewConfiguration.getMaximumFlingVelocity();
    }

    @Override
    public void scrollBy(int x, int y) {
	// 判断当前视图是否超过了顶部或者顶部就让它滑动的距离为1/3这样就有越拉越拉不动的效果
	if (getScrollY() < 0 || getScrollY() + getHeight() > mTotalLength) {
	    super.scrollBy(x, y / 3);
	} else {
	    super.scrollBy(x, y);
	}
    }

    /**
     * 事件拦截
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
	final int action = ev.getAction();
	// 表示已经开始滑动了,不需要走该Action_MOVE方法了(第一次时可能调用)。
	if ((action == MotionEvent.ACTION_MOVE)
		&& (mTouchState != TOUCH_STATE_DEFALUT)) {
	    return true;
	}
	int y = (int) ev.getY();
	switch (action) {
	case MotionEvent.ACTION_MOVE:
	    final int xDiff = (int) Math.abs(mLastMontionY - y);
	    // 超过了最小滑动距离
	    if (xDiff > mTouchSlop) {
		mTouchState = TOUCH_STATE_SROLLING;
	    }
	    break;
	case MotionEvent.ACTION_POINTER_DOWN:
	    mPointID = ev.getPointerId(ev.getActionIndex()); // 记录当前pointID
	    break;
	case MotionEvent.ACTION_DOWN:
	    mLastMontionY = y;
	    Log.e(TAG, mScroller.isFinished() + "");
	    if (!mScroller.isFinished()) // 当动画还没有结束的时候强制结束
	    {
		mScroller.abortAnimation();
		mScroller.forceFinished(true);
	    }
	    mTouchState = TOUCH_STATE_DEFALUT;
	    break;

	case MotionEvent.ACTION_CANCEL:
	case MotionEvent.ACTION_UP:
	    mTouchState = TOUCH_STATE_DEFALUT;
	    break;
	}
	Log.e(TAG, mTouchState + "====" + TOUCH_STATE_DEFALUT);
	return mTouchState != TOUCH_STATE_DEFALUT;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

	int touchIndex = event.getActionIndex();
	if (mVelocityTracker == null) {
	    mVelocityTracker = VelocityTracker.obtain();
	}
	mVelocityTracker.addMovement(event);

	switch (event.getAction()) {
	case MotionEvent.ACTION_DOWN:
	    mPointID = event.getPointerId(0);
	    mLastMontionY = (int) event.getY();// 记录按下的点
	    break;
	case MotionEvent.ACTION_POINTER_DOWN: // 添加多点触控的处理
	    mPointID = event.getPointerId(touchIndex);
	    mLastMontionY = (int) (event.getY(touchIndex) + 0.5f); // 记录按下的点
	    break;

	case MotionEvent.ACTION_MOVE:
	    touchIndex = event.findPointerIndex(mPointID);
	    if (touchIndex < 0) // 当前index小于0就返false继续接受下一次事件
		return false;
	    int detaY = (int) (mLastMontionY - event.getY(touchIndex)); // 计算滑动的距离
	    scrollBy(0, detaY); // 调用滑动函数
	    mLastMontionY = (int) event.getY(touchIndex); // 记录上一次按下的点
	    break;
	case MotionEvent.ACTION_UP:
	    Log.d("edsheng", "Action UP");
	    mVelocityTracker.computeCurrentVelocity(1000);
	    if (Math.abs(mVelocityTracker.getYVelocity()) > mMaxmumVelocity&&!checkIsBroad()) {
		mScroller.fling(getScrollX(), getScrollY(), 0,-(int) mVelocityTracker.getYVelocity(), 0, 0, 0,
			mTotalLength - getHeight());
	    } else {
		actionUP(); // 回弹效果
	    }

	    mTouchState = TOUCH_STATE_DEFALUT;

	    break;
	case MotionEvent.ACTION_POINTER_UP:
	    // 添加多点触控的支持
	    if (event.getPointerId(touchIndex) == mPointID) {
		final int newIndex = touchIndex == 0 ? 1 : 0;
		mPointID = event.getPointerId(newIndex);
		mLastMontionY = (int) (event.getY(newIndex) + 0.5f);
	    }
	    break;
	case MotionEvent.ACTION_CANCEL:
	    mTouchState = TOUCH_STATE_DEFALUT;
	    break;
	default:
	    break;
	}
	// super.onTouchEvent(event);
	return true;
    }

    /**
     * 回弹函数
     */
    private void actionUP() {
	if (getScrollY() < 0 || getHeight() > mTotalLength) // 顶部回弹
	{
	    Log.d("edsheng", "顶部回弹!!!!");
	    mScroller.startScroll(0, getScrollY(), 0, -getScrollY()); // 开启回弹效果
	    invalidate();
	} else if (getScrollY() + getHeight() > mTotalLength) // 底部回弹
	{
	    // 开启底部回弹
	    mScroller.startScroll(0, getScrollY(), 0, -(getScrollY()
		    + getHeight() - mTotalLength));
	    invalidate();
	}
    }

    /***
     * 检测当前是否可回弹
     *
     * @return
     */
    boolean checkIsBroad() {
	if (getScrollY() < 0 || getScrollY() + getHeight() > mTotalLength) // 顶部回弹)
									   // //顶部回弹
	    return true;
	else
	    return false;
    }

    /**
     * 重写onMeasure方法计算
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	final int size = getChildCount();
	final int parentWidthSize = MeasureSpec.getSize(widthMeasureSpec);
	final int paretnHeightSize = MeasureSpec.getSize(heightMeasureSpec);
	for (int i = 0; i < size; ++i) {
	    final View child = getChildAt(i);
	    if (child.getVisibility() != GONE) {
		LayoutParams childLp = child.getLayoutParams();
		final boolean childWidthWC = childLp.width == LayoutParams.WRAP_CONTENT;
		final boolean childHeightWC = childLp.height == LayoutParams.WRAP_CONTENT;
		int childWidthMeasureSpec;
		int childHeightMeasureSpec;
		if (child.getLayoutParams() instanceof MarginLayoutParams) {
		    MarginLayoutParams childMarginLp = (MarginLayoutParams) childLp;
		    childWidthMeasureSpec = childWidthWC ? MeasureSpec
			    .makeMeasureSpec(parentWidthSize,
				    MeasureSpec.UNSPECIFIED)
			    : getChildMeasureSpec(widthMeasureSpec,
				    getPaddingLeft() + getPaddingRight()
					    + childMarginLp.leftMargin
					    + childMarginLp.rightMargin,
				    childLp.width);
		    childHeightMeasureSpec = childHeightWC ? MeasureSpec
			    .makeMeasureSpec(paretnHeightSize,
				    MeasureSpec.UNSPECIFIED)
			    : getChildMeasureSpec(heightMeasureSpec,
				    getPaddingTop() + getPaddingBottom()
					    + childMarginLp.topMargin
					    + childMarginLp.bottomMargin,
				    childMarginLp.height);
		} else {
		    childWidthMeasureSpec = childWidthWC ? MeasureSpec
			    .makeMeasureSpec(parentWidthSize,
				    MeasureSpec.UNSPECIFIED)
			    : getChildMeasureSpec(widthMeasureSpec,
				    getPaddingLeft() + getPaddingRight(),
				    childLp.width);
		    childHeightMeasureSpec = childHeightWC ? MeasureSpec
			    .makeMeasureSpec(paretnHeightSize,
				    MeasureSpec.UNSPECIFIED)
			    : getChildMeasureSpec(heightMeasureSpec,
				    getPaddingTop() + getPaddingBottom(),
				    childLp.height);
		}
		child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
	    }
	}

	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    /***
     * 重写layout方法
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

	int childStartPostion = 0;
	mTotalLength = 0;
	final int count = getChildCount();
	if (count == 0) {
	    return;
	}
	childStartPostion = getPaddingTop();
	for (int i = 0; i < count; i++) {
	    final View child = getChildAt(i);
	    if (child != null && child.getVisibility() != View.GONE) {
		LayoutParams lp = child.getLayoutParams();
		final int childHeight = child.getMeasuredHeight();
		int leftMargin = 0;
		int rightMargin = 0;
		int topMargin = 0;
		int bottomMargin = 0;
		if (lp instanceof MarginLayoutParams) {
		    MarginLayoutParams mlp = (MarginLayoutParams) lp;
		    leftMargin = mlp.leftMargin;
		    rightMargin = mlp.rightMargin;
		    topMargin = mlp.topMargin;
		    bottomMargin = mlp.bottomMargin;
		}

		childStartPostion += topMargin;
		int startX = (getWidth() - leftMargin - rightMargin - child
			.getMeasuredWidth()) / 2 + leftMargin;
		child.layout(startX, childStartPostion,
			startX + child.getMeasuredWidth(), childStartPostion
				+ childHeight);
		childStartPostion += (childHeight + bottomMargin);
	    }
	}
	childStartPostion += getPaddingBottom();
	mTotalLength = childStartPostion;

    }

    @Override
    public void computeScroll() {
	if (mScroller.computeScrollOffset()) // 计算当前位置
	{
	    // 滚动
	    scrollTo(0, mScroller.getCurrY());
	    postInvalidate();
	}
    }
}

然后我们在我们的Activity里面这样使用就可以了。

public class MainActivity extends Activity
{

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);

		QSrollView qSrollView = new QSrollView(this);
//		qbSrollView.setBackgroundColor(Color.RED);

		qSrollView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

		LinearLayout.LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
		layoutParams.bottomMargin = 20;

		LayoutParams commomlayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
		commomlayoutParams.bottomMargin = 20;

		StyleButton button = new StyleButton(this);
		button.setText("按钮");
		qSrollView.addView(button,commomlayoutParams);

		StyleButton button2 = new StyleButton(this);
		button2.setText("按钮");
		button2.setRadius(16);
		qSrollView.addView(button2,commomlayoutParams);

		StyleButton button3 = new StyleButton(this);
		button3.setText("按钮");
		button3.setRadius(32);
		button3.setTextNormalPressedcolor(Color.CYAN, Color.WHITE);
		button3.setBgNormalPressedcolor(Color.BLUE, Color.CYAN);
		qSrollView.addView(button3,commomlayoutParams);

//		for(int i= 0 ; i < 100; i ++)
		{
		StyleButton button4 = new StyleButton(this);
//		button4.setText("按钮"+i);
		button4.setRadius(80);
		button4.setBgNormalPressedcolor(Color.GRAY, Color.CYAN);
		button4.setTextNormalPressedcolor(Color.RED, Color.WHITE);
		qSrollView.addView(button4,commomlayoutParams);
		}

		StyleButton button5 = new StyleButton(this);
		button5 = new StyleButton(this);
		button5.setText("按钮");
		button5.setRadius(50);
		button5.setTextNormalPressedcolor(Color.BLACK, Color.BLUE);
		button5.setBgNormalPressedcolor(Color.WHITE, Color.CYAN);
		qSrollView.addView(button5,commomlayoutParams);

		StyleButton button6 = new StyleButton(this);
		button6.setText("按钮");
		button6.setRadius(50);
		button6.setTextNormalPressedcolor(Color.BLACK, Color.CYAN);
		button6.setBgNormalPressedcolor(Color.CYAN, Color.BLUE);
		LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
		params.leftMargin = 80;
		params.rightMargin = 80;
		qSrollView.addView(button6,commomlayoutParams);

		StyleButton button7 = new StyleButton(this);
		button7.setText("按钮");
		button7.setRadius(80);
		qSrollView.addView(button7,commomlayoutParams);

		setContentView(qSrollView);

	};
}

里面用到的按钮还是我上一篇里面的button可以看看真正的圆角Button好了今天的教程就到这里了,写得不好还请原谅。有什么不足我们也一起进步。demo下载地址

时间: 2024-10-12 20:43:38

android自定义控件系列教程----继承ViewGroup实现带阻力效果的可回弹的SrollView的相关文章

android自定义控件系列教程----视图的测量和布局

前面说点什么 当我们的一个视图界面绘制在android屏幕上面的时候其实都必须经过这几步measure. layout.draw这几个阶段,我们可以在view类里面看到这几个函数,然后里面有几个函数是onmeasure.onlayout.ondraw这几个函数是我们重写控件需要注意的这几个函数,下面我们就来讲讲这几个函数的功能和作用. onMeasure 正如这个函数的名子一样就是测量,所有的图示其实系统在绘制之前都不知道它到底有多大的,所以在很多时候我们在初始化界面oncreate的时候直接去

[转]Android自定义控件系列五:自定义绚丽水波纹效果

出处:http://www.2cto.com/kf/201411/353169.html 今天我们来利用Android自定义控件实现一个比较有趣的效果:滑动水波纹.先来看看最终效果图: 图一 效果还是很炫的:饭要一口口吃,路要一步步走,这里我们将整个过程分成几步来实现 一.实现单击出现水波纹单圈效果: 图二 照例来说,还是一个自定义控件,这里我们直接让这个控件撑满整个屏幕(对自定义控件不熟悉的可以参看我之前的一篇文章:Android自定义控件系列二:自定义开关按钮(一)).观察这个效果,发现应该

Android自定义控件系列五:自定义绚丽水波纹效果

尊重原创!转载请注明出处:http://blog.csdn.net/cyp331203/article/details/41114551 今天我们来利用Android自定义控件实现一个比较有趣的效果:滑动水波纹.先来看看最终效果图: 图一 效果还是很炫的:饭要一口口吃,路要一步步走,这里我们将整个过程分成几步来实现 一.实现单击出现水波纹单圈效果: 图二 照例来说,还是一个自定义控件,这里我们直接让这个控件撑满整个屏幕(对自定义控件不熟悉的可以参看我之前的一篇文章:Android自定义控件系列二

android自定义控件系列教程----视图

理解android视图 对于android设备我们所看到的区域其实和它在底层的绘制有着很大的关系,很多时候我们都只关心我们所看到的,那么在底层一点它到底是怎么样的一个东西呢?让我们先来看看这个图. 对于整个设备的可见区域而言其实就是我们中间的那个屏幕,从上面的拿个图可以清晰的看到,除了我们的可见区域在它的上下左右都应该有内容,那么在android系统中是怎么控制显示它的位置呢?下面我们来解答这个问题. android如何控制视图的显示位置 我们可以打开view类的源码找到这两个函数 /** *

android自定义控件系列教程-----touch事件的传递

前沿: 很久没有写过博客了,因为工作的原因很少有时间写东西了,最近想写一个UI系列的博客,因为我发现这一系列的都很少,而且没有那么系统,这里我想以我自己的观点来阐述一下如何自定义android 控件系列. 自定义控件阐述: 在我的理解里面自定义控件,需要了解到touch事件的传递.分发.拦截机制,Scroller类的运用,andorid 视图的理解,ViewGroup的熟悉,因为我们绝大多的控件都是继承自ViewGroup,还有就是要学会布局测量等. Touch事件的传递 首先我们要了解在and

android自定义控件系列教程-----仿新版优酷评论剧集卡片滑动控件

我们先来看看优酷的控件是怎么回事? 只响应最后也就是最顶部的卡片的点击事件,如果点击的不是最顶部的卡片那么就先把它放到最顶部,然后在移动到最前面来,反复如次. 知道了这几条那么我们就很好做了. 里面的技术细节可能就是child的放置到前面来的动画问题把. 先看看我们实现得效果: 然后仔细分析一下我们要实现怎么样的效果: 我也是放置了一个按钮和两个view在控件上面,只有当控件在最前面也就是最里面的时候才会响应事件. 然后我们就动手来实现这个控件. 我们继承一个ViewGroup并且命名为Exch

android自定义控件系列教程----真正的圆角button来了

前沿: 现在网上随便输入一句圆角button就会出现很多博客和文章提示做这样的一个效果,但是那多半都是xml文件来做的,这样做有个很大的弊端,因为每一次都需要重写xml文件(就连简简单单的修改个按钮的颜色也需要修改).~~为什么呢?因为不修改臣妾做不到啊!!!今天就带大家做一个真正的圆角button,我们还是来看效果吧. 正文干货开始: 很明显我们的按钮的背景就是我们要实现的圆角部分,那么我们情不自禁的想到了setBackground这个方法,看看里面的参数,需要的是一个Drawable,而我们

Android自定义控件系列七:详解onMeasure()方法中如何测量一个控件尺寸(一)

转载请注明出处:http://blog.csdn.net/cyp331203/article/details/45027641 自定义view/viewgroup要重写的几个方法:onMeasure(),onLayout(),onDraw().(不熟悉的话可以查看专栏的前几篇文章:Android自定义控件系列二:自定义开关按钮(一)). 今天的任务就是详细研究一下protected void onMeasure(int widthMeasureSpec, int heightMeasureSpe

史上最详细的Android Studio系列教程一--下载和安装

链接地址:http://segmentfault.com/a/1190000002401964#articleHeader4 原文链接:http://stormzhang.com/devtools/2014/11/25/android-studio-tutorial1/ 背景 相信大家对Android Studio已经不陌生了,Android Studio是Google于2013 I/O大会针对Android开发推出的新的开发工具,目前很多开源项目都已经在采用,Google的更新速度也很快,明显