github地址:https://github.com/zerohuan/SlideLayout/tree/master
實際效果圖:
該自定義控件繼承FrameLayout, 包含一個ViewPager和橫向排列的LinearLayout。後者用於包含顯示錶示輪播位置的點集,使用ViewPager的好處在於可以靈活的定義item的內容,而不僅僅是圖片。</p><p></p><p>爲了便於使用,通過自定義屬性的方式定義了所須的運行參數:</p><p><pre name="code" class="html"><declare-styleable name="SlideLayout">
<attr name="viewpagerId" format="reference"/>
<attr name="dotsId" format="reference"/>
<attr name="autoPlay" format="boolean" />
<attr name="slide_interval" format="integer" />
<attr name="dotRadius" format="dimension" />
<attr name="onDotColor" format="color" />
<attr name="offDotColor" format="color" />
<attr name="strokeColor" format="color" />
</declare-styleable>
對應的數據成員如下:
//輪播容器viewPager
private ViewPager viewPager;
//標示位置的點集
private LinearLayout dots;
//viewPager的資源ID
private int viewPagerId;
//包含點的LinearLayout的資源ID
private int dotsId;
//該手機的px-dp比例倍數
private float scale;
//ViewPager的Adapter
private PagerAdapter adapter;
//是否自動播放
private boolean isAutoPlay;
//播放的間隔
private int interval;
//當前頁位置
private int currentItem;
//圓點半徑
private float dotRadius;
//是否正在輪播運行
private boolean isRunning;
//位於當前頁,點標誌的顏色
private int onDotColor;
//未位於當前頁,點標誌的顏色
private int offDotColor;
//點邊框顏色
private int strokeColor;
private final static int SCROLL_WHAT = 0x7549;
使用時,通過Xml文件來定義:
<com.luckymore.ydd.app.view.selfView.SlideLayout
xmlns:attrs="http://schemas.android.com/apk/res-auto"
android:id="@+id/news_slide_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
swipe:dotsId="@+id/test_1"
attrs:viewpagerId="@+id/test_2"
attrs:autoPlay="true"
attrs:dotRadius="3dp"
attrs:slide_interval="4000"
attrs:offDotColor="@color/alpha_black"
attrs:onDotColor="@color/alpha_white"
attrs:strokeColor="#77626262"
>
<android.support.v4.view.ViewPager android:id="@+id/test_2"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout android:id="@+id/test_1"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:orientation="horizontal"
android:layout_gravity="bottom|center_horizontal"
android:gravity="center"
android:paddingLeft="8dp"
android:paddingRight="8dp"
/>
</com.luckymore.ydd.app.view.selfView.SlideLayout>
獲取自定義參數值:
/**
* 獲取自定義參數,在構造器中調用
* @param context
* @param attrs
*/
private void init(Context context, AttributeSet...attrs) {
if(attrs.length > 0) {
TypedArray typedArray = context.obtainStyledAttributes(attrs[0], R.styleable.SlideLayout);
viewPagerId = typedArray.getResourceId(R.styleable.SlideLayout_viewpagerId, -1);
dotsId = typedArray.getResourceId(R.styleable.SlideLayout_dotsId, -1);
scale = getResources().getDisplayMetrics().density;
isAutoPlay = typedArray.getBoolean(R.styleable.SlideLayout_autoPlay, false);
interval = typedArray.getInteger(R.styleable.SlideLayout_slide_interval, 4000);
dotRadius = typedArray.getDimension(R.styleable.SlideLayout_dotRadius, 2f * scale);
onDotColor = typedArray.getColor(R.styleable.SlideLayout_onDotColor, 0x77FFFFFF);
offDotColor = typedArray.getColor(R.styleable.SlideLayout_offDotColor, 0x77000000);
strokeColor = typedArray.getColor(R.styleable.SlideLayout_strokeColor, 0x77626262);
}
}
ViewPager的ID和LinearLayout的ID通過自定義屬性的方式傳入,注入到SlideLayout中,在onFinishInflate中實現:
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if(viewPagerId != -1 && dotsId != -1) {
viewPager = (ViewPager)findViewById(viewPagerId);
dots = (LinearLayout)findViewById(dotsId);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i2) {
}
@Override
public void onPageSelected(int i) {
currentItem = i;
for(int j = 0; j < viewPager.getAdapter().getCount(); j++) {
DotView dot = (DotView)dots.getChildAt(j);
if(j == i)
dot.setOn(true);
else
dot.setOn(false);
dot.invalidate();
}
}
@Override
public void onPageScrollStateChanged(int i) {
switch (i) {
case 1:// 手勢滑動,空閒中
isAutoPlay = false;
break;
case 2:// 界面切換中
isAutoPlay = true;
break;
case 0:// 滑動結束,即切換完畢或者加載完畢
// 當前爲最後一張,此時從右向左滑,則切換到第一張
if (viewPager.getCurrentItem() == viewPager.getAdapter().getCount() - 1 && !isAutoPlay) {
viewPager.setCurrentItem(0);
}
// 當前爲第一張,此時從左向右滑,則切換到最後一張
else if (viewPager.getCurrentItem() == 0 && !isAutoPlay) {
viewPager.setCurrentItem(viewPager.getAdapter().getCount() - 1);
}
break;
}
}
});
}
}
接着實現,LinearLayout中的“點”的填充,首先定義內部類DotView:
/**
* 點狀UI, 正方形View, 包含一個圓形點
*/
public class DotView extends View {
private Paint mPaint = new Paint();
//兩種狀態, 是否是當前頁
private boolean isOn;
//正方形邊長
private int mSize;
public DotView(Context context) {
super(context);
}
public DotView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public DotView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 比onDraw先執行
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mSize = (int)Math.ceil(dotRadius) * 3;
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) {
// We were told how big to be
result = specSize;
} else {
// Measure the text
result = mSize + getPaddingLeft() + getPaddingRight();
if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by
// measureSpec
result = Math.min(result, specSize);// 60,480
}
}
return result;
}
private int measureHeight(int measureSpec) {
int result = 0;
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 text (beware: ascent is a negative number)
result = mSize + getPaddingTop() + getPaddingBottom();
if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by
// measureSpec
result = Math.min(result, specSize);
}
}
return result;
}
//繪製點
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setAntiAlias(true);
if(isOn()) {
mPaint.setColor(onDotColor);
} else {
mPaint.setColor(offDotColor);
}
mPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(dotRadius, dotRadius, dotRadius, mPaint);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(0.7f * scale);
mPaint.setColor(strokeColor);
canvas.drawCircle(dotRadius, dotRadius, dotRadius, mPaint);
}
public boolean isOn() {
return isOn;
}
public void setOn(boolean isOn) {
this.isOn = isOn;
}
}
DotView包含是否是當前頁兩種狀態,填充顏色和邊框顏色都從自定義屬性中獲得。
在SlideLayout的成員函數resetDots填充DotView:
/**
* 重新生成點集UI
*/
public void resetDots() {
int dotCount = viewPager.getAdapter().getCount();
int oldDotCount = dots.getChildCount();
for(int i = 0; i < dotCount; i ++) {
DotView dot;
if(i >= oldDotCount) {
dot = new DotView(getContext());
dots.addView(dot);
} else {
dot = (DotView)dots.getChildAt(i);
}
if(i == viewPager.getCurrentItem()) {
dot.setOn(true);
} else {
dot.setOn(false);
}
}
}
當LinearLayout點集的dotView數量小於ViewPager中Pager數量時,新建DotView,填充至LinearLayout;
在UI線程中使用SlideLayout主要調用如下兩個方法:
/**
* 在UI線程中調用,注入ViewPager的adapter
* @param adapter
*/
public void setViewPagerAdapter(PagerAdapter adapter) {
this.adapter = adapter;
viewPager.setAdapter(adapter);
updateUI();
}
/**
* 在UI線程中調用,修改adapter中數據後, 更新UI
*/
public synchronized void updateUI() {
adapter.notifyDataSetChanged();
resetDots();
if(isAutoPlay)
startAutoScroll();
}
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case SCROLL_WHAT:
synchronized(SlideLayout.this) {
viewPager.setCurrentItem(currentItem);
sendScrollMessage(interval);
}
break;
}
}
};
/**
* 開始自動輪播
*/
public void startAutoScroll() {
if(!isRunning) {
isRunning = true;
sendScrollMessage(interval);
}
}
/**
* 滾動到下一頁
* @param delayTimeInMills
*/
private synchronized void sendScrollMessage(long delayTimeInMills) {
/** remove messages before, keeps one message is running at most **/
handler.removeMessages(SCROLL_WHAT);
currentItem = (currentItem + 1) % adapter.getCount();
//通過該方法實現定時輪播
handler.sendEmptyMessageDelayed(SCROLL_WHAT, delayTimeInMills);
}
newsBar.setViewPagerAdapter(new NewsBarAdapter(ret.getNews(), getMactivity()));