实用控件干货区自定义控件

Android打造不一样的新手引导页面(一)

2016-08-20  本文已影响891人  程序员徐公

Android打造不一样的新手引导页面(一)


转载请注明原博客地址:

本篇博客主要讲解怎样自定义一个circleIndicator控件?

下一遍博客主要讲解怎样更改ViewPager切换的效果, 预计明天晚上之前更新。

效果图如下

1)首先我们先来看一下要怎样使用我们的circleIndicator控件

其实很简单,值需要两个步骤

1) 在xml布局文件里面

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <android.support.v4.view.ViewPager
        android:layout_below="@id/rl_header"
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"

        android:layout_marginTop="20dp">

    </android.support.v4.view.ViewPager>

    <com.xujun.administrator.customviewspecif.view.CirclePageIndicator
        android:id="@+id/circle_indicator"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="20dp">

    </com.xujun.administrator.customviewspecif.view.CirclePageIndicator>
</RelativeLayout>

2)在代码里面

mViewPager = (ViewPager) findViewById(R.id.viewPager);
mCirclePageIndicator = (CirclePageIndicator) findViewById(R.id.circle_indicator);

//注意下面初始化的顺序不可以调换
mFragemntAdapter = new BaseFragemntAdapter(
        getSupportFragmentManager(), mFragments);
mViewPager.setAdapter(mFragemntAdapter);

//将mCirclePageIndicator与我们的mViewPager绑定在一起
mCirclePageIndicator.setViewPager(mViewPager);

扩展

1)在xml布局里面更改我们的样式

xmlns:app="http://schemas.android.com/apk/res-auto"
//例如更改我们移动小圆点的颜色
app:fillColor="#fff"


//其他属性的更改请参考以下我们自定义的属性

<declare-styleable name="CirclePageIndicator">
    <!-- Whether or not the indicators should be centered. -->
    <attr name="centered" />
    <!-- Color of the filled circle that represents the current page. -->
    <attr name="fillColor" format="color" />
    <!-- Color of the filled circles that represents pages. -->
    <attr name="pageColor" format="color" />
    <!-- Orientation of the indicator. -->
    <attr name="android:orientation"/>
    <!-- Radius of the circles. This is also the spacing between circles. -->
    <attr name="radius" format="dimension" />
    <!-- Whether or not the selected indicator snaps to the circles. -->
    <attr name="snap" format="boolean" />
    <!-- Color of the open circles. -->
    <attr name="strokeColor" format="color" />
    <!-- Width of the stroke used to draw the circles. -->
    <attr name="strokeWidth" />
    <!-- View background -->
    <attr name="android:background"/>
</declare-styleable>


2)在Java代码里面动态更改

// 设置滑动的时候移动的小圆点是否跳跃
mCirclePageIndicator.setSnap(false);
//设置小圆点的半径
mCirclePageIndicator.setRadius(10 * density);
// 设置页面小圆点的颜色
mCirclePageIndicator.setPageColor(0x880000FF);
// 设置移动的小圆点的颜色
mCirclePageIndicator.setFillColor(0xFF888888);
// 设置外边框的颜色
mCirclePageIndicator.setStrokeColor(0xFF000000);
//设置外表框的宽度
mCirclePageIndicator.setStrokeWidth(2 * density);


2)下面我们一起来看我们是怎样CircleIndicator是怎样实现的

大概可以分为以下几个步骤

public CirclePageIndicator(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    if (isInEditMode()) return;

    //初始化自定义属性
    final Resources res = getResources();

    final int defaultPageColor = res.getColor(R.color.default_circle_indicator_page_color);
    final int defaultFillColor = res.getColor(R.color.default_circle_indicator_fill_color);
    final int defaultOrientation = res.getInteger(R.integer
            .default_circle_indicator_orientation);

  在这里省略了若干方法

    a.recycle();

    final ViewConfiguration configuration = ViewConfiguration.get(context);
    mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == HORIZONTAL) {
        setMeasuredDimension(measureLong(widthMeasureSpec), measureShort(heightMeasureSpec));
    } else {
        setMeasuredDimension(measureShort(widthMeasureSpec), measureLong(heightMeasureSpec));
    }
}


/**
 * Determines the width of this view
 *
 * @param measureSpec A measureSpec packed into an int
 * @return The width of the view, honoring constraints from measureSpec
 */
private int measureLong(int measureSpec) {
    int result;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) {
        //We were told how big to be
        result = specSize;
    } else {
        //Calculate the width according the views count
        final int count = mViewPager.getAdapter().getCount();
        result = (int) (getPaddingLeft() + getPaddingRight()
                + (count * 2 * mRadius) + (count - 1) * mRadius + 1);
        //Respect AT_MOST value if that was what is called for by measureSpec
        if (specMode == MeasureSpec.AT_MOST) {
            result = Math.min(result, specSize);
        }
    }
    return result;
}

/**
 * Determines the height of this view
 *
 * @param measureSpec A measureSpec packed into an int
 * @return The height of the view, honoring constraints from measureSpec
 */
private int measureShort(int measureSpec) {
    int result;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    if (specMode == MeasureSpec.EXACTLY) {
        //We were told how big to be
        result = specSize;
    } else {
        //Measure the height
        result = (int) (2 * mRadius + getPaddingTop() + getPaddingBottom() + 1);
        //Respect AT_MOST value if that was what is called for by measureSpec
        if (specMode == MeasureSpec.AT_MOST) {
            result = Math.min(result, specSize);
        }
    }
    return result;
}



@Override
public void setViewPager(ViewPager view) {
    if (mViewPager == view) {
        return;
    }
    if (mViewPager != null) {
        mViewPager.addOnPageChangeListener(null);
    }
    if (view.getAdapter() == null) {
        throw new IllegalStateException("ViewPager does not have adapter instance.");
    }
    mViewPager = view;
    mViewPager.addOnPageChangeListener(this);
    invalidate();
}

里面主要的逻辑简单来说就是判断我们的ViewPager是否已经设置adapter,没有的话抛出异常,接着监听ViewPager的PageChangListener事件。
调用invalidate()方法重新绘制CirclePagerIndicator

@Override
public void onPageScrollStateChanged(int state) {
    mScrollState = state;

    if (mListener != null) {
        mListener.onPageScrollStateChanged(state);
    }
}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    mCurrentPage = position;
    mPageOffset = positionOffset;
    invalidate();

    if (mListener != null) {
        mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
    }
}

@Override
public void onPageSelected(int position) {
    if (mSnap || mScrollState == ViewPager.SCROLL_STATE_IDLE) {
        mCurrentPage = position;
        mSnapPage = position;
        invalidate();
    }

    if (mListener != null) {
        mListener.onPageSelected(position);
    }
}


@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    if (mViewPager == null) {
        return;
    }
    final int count = mViewPager.getAdapter().getCount();
    if (count == 0) {
        return;
    }

    if (mCurrentPage >= count) {
        setCurrentItem(count - 1);
        return;
    }

    int longSize;
    int longPaddingBefore;
    int longPaddingAfter;
    int shortPaddingBefore;
    //  根据方向的不同初始化各个变量
    if (mOrientation == HORIZONTAL) {
        longSize = getWidth();
        longPaddingBefore = getPaddingLeft();
        longPaddingAfter = getPaddingRight();
        shortPaddingBefore = getPaddingTop();
    } else {
        longSize = getHeight();
        longPaddingBefore = getPaddingTop();
        longPaddingAfter = getPaddingBottom();
        shortPaddingBefore = getPaddingLeft();
    }

    final float threeRadius = mRadius * 3;
    final float shortOffset = shortPaddingBefore + mRadius;
    float longOffset = longPaddingBefore + mRadius;
    /**
     * 居中显示的时候
     */
    if (mCentered) {
        longOffset += ((longSize - longPaddingBefore - longPaddingAfter) / 2.0f)
                - ((count * threeRadius) / 2.0f);
    }

    float dX;
    float dY;

    float pageFillRadius = mRadius;
    if (mPaintStroke.getStrokeWidth() > 0) {
        pageFillRadius -= mPaintStroke.getStrokeWidth() / 2.0f;
    }

    //Draw stroked circles
    for (int iLoop = 0; iLoop < count; iLoop++) {
        float drawLong = longOffset + (iLoop * threeRadius);
        if (mOrientation == HORIZONTAL) {
            dX = drawLong;
            dY = shortOffset;
        } else {
            dX = shortOffset;
            dY = drawLong;
        }
        // Only paint fill if not completely transparent
        if (mPaintPageFill.getAlpha() > 0) {
            canvas.drawCircle(dX, dY, pageFillRadius, mPaintPageFill);
        }

        // Only paint stroke if a stroke width was non-zero
        if (pageFillRadius != mRadius) {
            canvas.drawCircle(dX, dY, mRadius, mPaintStroke);
        }
    }

    //下面绘制移动的实心圆

    float cx = (mSnap ? mSnapPage : mCurrentPage) * threeRadius;
    //根据移动的实心圆是否跳跃计算偏移量
    if (!mSnap) {
        cx += mPageOffset * threeRadius;
    }
    if (mOrientation == HORIZONTAL) {
        dX = longOffset + cx;
        dY = shortOffset;
    } else {
        dX = shortOffset;
        dY = longOffset + cx;
    }
    canvas.drawCircle(dX, dY, mRadius, mPaintFill);
}

其实核心就是根据方向的不同绘制我们的小圆点,那些偏移量是一些数学运算而已,不过别小看这些,计算这些偏移量还是挺繁琐的。

到此我们的源码分析为止


题外话

如果各位觉得还行的话,欢迎在github上面 star或者 fork,谢谢 ,github项目地址ViewPagerTabIndicator

源码github参考库ViewPagerIndicator:

转载请注明原博客地址:

源码下载地址:

上一篇下一篇

猜你喜欢

热点阅读