今天无聊自己写了一个模仿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);
}
}
好了这里就写完了,有不足的地方请大家指正!下面是效果图