今天无聊自己写了一个模仿viewpager的指示器,这个例子是利用我以前写的一片文章的例子进行改进《android横向滚动屏幕特效分析》。
下面说下我的思路,首先当控件初始化的时候获取ScrollLayout空间的子View控件个数,然后传入我们要写的IndictorView类里面,然后利用onDraw()方法在屏幕上画同等数量的圆形,表示当前屏幕的圆圈我们用不同的颜色区分就行了,然后每次移动的时候我们改变画表示当前屏幕圆圈的位置即可。
既然要画两种颜色的圆圈那么就要生成两种颜色的画笔,下面是我生成画笔的代码:
/** * 生成标识当前屏幕的画笔和背景圆圈的画笔,此方法在构造方法中调用 */ private void init() { // 这句是让画笔画得更细腻(抗锯齿) fontPaint = new Paint(Paint.ANTI_ALIAS_FLAG); // 这句话的意思是已填充的方式画园,Style.STROKE方式是画一个圆环中间没有填充 fontPaint.setStyle(Style.FILL); // 设置画笔的颜色 fontPaint.setColor(Color.RED); backPaint = new Paint(Paint.ANTI_ALIAS_FLAG); backPaint.setStyle(Style.FILL); backPaint.setColor(Color.BLACK); }
在构造好IndictorView之后我们需要把ScorllView的子元素个数传过来,那么就需要提供一个方法让外部可以把我们需要的数据传如,下面是实现代码:
/** * 初始化圆的个数,这里的屏幕宽度是为了后面我们计算圆圈的位置时使用 * * @param circleNumber * 园的数量 * @param motionWidth * 屏幕的宽度 */ public void init(int circleNumber, float motionWidth) { this.circleNumber = circleNumber; this.motionWidth = motionWidth; }
现在我们有了园的个数,又得到屏幕的宽度,那么就可以开始画园了,下面是实现代码:
@Override public void draw(Canvas canvas) { // 得到圆的Y轴坐标 int height = getHeight() - getPaddingTop() - (radius * 2); // 这里是根据需要画圆的个数使用一个循环遍历来画园 for (int i = 1; i <= circleNumber; i++) { // 当当前的圆的下标等于要画的园的下标时候,说明这个下标就是标识当前屏幕下标的园 if (curentCircleNumber == i) { // 这里的表达式主要是计算园的X轴坐标 canvas.drawCircle((motionWidth - (radius * 2 * (circleNumber - i) + (circlePadding * (circleNumber - i - 1)))) / 2.0f + 10, height, radius, fontPaint); } else { // 画背景圆圈 canvas.drawCircle((motionWidth - (radius * 2 * (circleNumber - i) + (circlePadding * (circleNumber - i - 1)))) / 2.0f + 10, height, radius, backPaint); } } super.draw(canvas); // 刷新屏幕 invalidate(); }
上面就是核心代码,那么有人会问当OnDraw()执行过后怎样改变圆圈的位置能,其实View的onDraw()方法是在这个view被渲染后一直在后台执行的,所以不同担心我们改变curentCircleNumber的值时会不会从绘圆的位置的问题。
最后大家别忘了一件事,我们在重写空间的时候一定要记得覆盖onMeasure()方法,这里就不多说了,下面是源码:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); } private int measureWidth(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); // 当控件设置全屏时 if (specMode == MeasureSpec.EXACTLY) { result = specSize; } else { float temp = circleNumber - 2 * radius; result = (int) (getPaddingLeft() + getPaddingRight() + (circleNumber * 2 * radius) + (circleNumber - 1) * temp + 1); // 当设置根据内容自动适应 if (specMode == MeasureSpec.AT_MOST) { result = Math.min(result, specSize); } } return result; } private int measureHeight(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); // 当空间设置全屏时 if (specMode == MeasureSpec.EXACTLY) { result = specSize; } else { result = (int) (4 * radius + getPaddingTop() + getPaddingBottom() + 1); // 当设置根据内容自动适应 if (specMode == MeasureSpec.AT_MOST) { result = Math.min(result, specSize); } } return result; }
这时我们的IndictorView类我们就完全写好了,下面是我的布局文件menu.xml:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" > <cn.com.karl.scrollview.ScrollLayout android:id="@+id/scorllView" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@android:color/white" > <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="fitCenter" android:src="@drawable/test1" /> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="fitCenter" android:src="@drawable/test2" /> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="fitCenter" android:src="@drawable/test3" /> <ImageView android:layout_width="fill_parent" android:layout_height="fill_parent" android:scaleType="fitCenter" android:src="@drawable/test1" /> </cn.com.karl.scrollview.ScrollLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_alignBottom="@+id/scorllView" android:background="#88252525" > <cn.com.karl.scrollview.IndictorView android:id="@+id/indictorView" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center" /> </LinearLayout></RelativeLayout>
这里值得注意的是android:background="#88252525"这个背景色是为了给我们的IndictorView控件加上透明背景色,这个可以选择性添加。
下面是主程序使用源码:
package cn.com.karl.activity;import android.app.Activity;import android.os.Bundle;import android.view.Display;import cn.com.karl.R;import cn.com.karl.scrollview.IndictorView;import cn.com.karl.scrollview.ScrollLayout;public class TestScrollLayout extends Activity { private ScrollLayout scrollLalyout; private IndictorView indictorView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.menu); initView(); } private void initView() { scrollLalyout = (ScrollLayout) findViewById(R.id.scorllView); indictorView = (IndictorView) findViewById(R.id.indictorView); Display display = getWindowManager().getDefaultDisplay(); indictorView.init(scrollLalyout.getChildCount(), display.getWidth()); scrollLalyout.setIndictorView(indictorView); }}
scrollLalyout.setIndictorView(indictorView);这一句是把我们的指示器放到ScrollLayout里面去,当屏幕移动的时候可以调用我们的IndictorView的setCurentCircleNumber()方法来时时改变下标.
下面是ScrollLayout里面的移动屏幕的方法源码:
/** * 移动到下一个屏幕 * * @param whichScreen * 下一个屏幕的下标识 */ public void snapToScreen(int whichScreen) { // get the valid layout page whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1)); if (getScrollX() != (whichScreen * getWidth())) { final int delta = whichScreen * getWidth() - getScrollX(); mScroller.startScroll(getScrollX(), 0, delta, 0, 1000); mCurScreen = whichScreen; invalidate(); // Redraw the layout indictorView.setCurentCircleNumber(whichScreen + 1); } }
好了这里就写完了,有不足的地方请大家指正!下面是效果图