目前第三方轮播图实在太多太多,而且功能也是非常完善,这时,我们还需要自己写轮播图吗?答案是肯定要写的。纵使你用千百遍,不如亲手写一遍。
轮播图主要是由图片和指示器构成,图片自动切换,并且支持点击。 写一个轮播图主要注意这几点:
1、设置轮播图的宽高,这里需要根据子视图的宽高进行设置;
2、拦截Touch事件,并处理Touch事件;
3、自动轮播,这里主要涉及Timer,TimerTask和Handler。
好了,现在开始写一个属于我们自己的轮播图。
我们新建一个DiscoveryViewGroup,继承ViewGroup,实现它的实列方法,onLayout方法和onMeasure方法。 在onMeasure获取子视图的宽高,并设置BarnnerViewGroup的宽高; 在onLayout设置子视图的布局。
public class DiscoveryViewGroup extends ViewGroup {
private int childrenCount = 0; //子视图总数
private int childWidth;//子视图宽度
private int childHeight;//子视图高度
public DiscoveryViewGroup(Context context) {
super(context);
}
public DiscoveryViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public DiscoveryViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取子视图总数
childrenCount = getChildCount();
if (childrenCount == 0) {
setMeasuredDimension(0, 0);
} else {
//测绘子视图宽高
measureChildren(widthMeasureSpec, heightMeasureSpec);
View view = getChildAt(0);
//获取子视图宽高
childWidth = view.getMeasuredWidth();
childHeight = view.getMeasuredHeight();
int width = childWidth * childrenCount;
//设置ViewGroup的宽高
setMeasuredDimension(width, childHeight);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//changed为true,视图重绘布局
if (changed) {
int leftMargin = 0;
for (int i = 0; i < childrenCount; i++) {
View view = getChildAt(i);
//设置子视图的布局
view.layout(leftMargin, 0, leftMargin + childWidth, childHeight);
leftMargin += childWidth;
}
}
}
}
复制代码
现在我们需要我们的轮播图可以滚动,并且处理Touch事件, 滚动视图用scrollBy和scrollTo,实现onTouchEvent方法。
private int X;//滚动的X位置
public int index = 0; //当前图片的位置
//返回true,拦截Touch事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//获取点击的X坐标
X = (int) event.getX();
break;
case MotionEvent.ACTION_UP:
//获取抬起的已滚动的位置
int scrollX = getScrollX();
//计算出当前图片的位置
index = (scrollX + childWidth / 2) / childWidth;
//判断图片的位置是否为最左和最右
if (index < 0) {
index = 0;
} else if (index > childrenCount - 1) {
index = childrenCount - 1;
}
//计算出需要滚动的x距离
int dx = index * childWidth - scrollX;
scrollTo(dx, 0);
break;
case MotionEvent.ACTION_MOVE:
//获取移动位置
int moveX = (int) event.getX();
//获取移动距离
int dis = moveX - X;
//设置图片跟随滑动
scrollBy(-dis, 0);
//此时X坐标等于移动的坐标
X = moveX;
break;
}
return true;
}
复制代码
目前已经实现基本的轮播图样式,但是还需要轮播图自动滚动和支持点击操作。 这里就需要Timer,TimerTask,Handler和Scroller配合使用了。
private Scroller scroller;
private boolean isAuto = true; //是否开始自动轮播
private Timer timer = new Timer();
private TimerTask task;
private boolean isClick = true; //是否可点击
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
if (++index >= childrenCount) {
index = 0;
}
int currX = scroller.getCurrX();
if (currX >= childWidth * (index - 1)) {
currX = 0;
}
scroller.startScroll(currX, 0, index * childWidth, 0);
//通知刷新视图
postInvalidate();
onDiscoveryListener.scrollImageIndex(index);
break;
}
}
};
public DiscoveryViewGroup(Context context) {
super(context);
init();
}
public DiscoveryViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DiscoveryViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
scroller = new Scroller(getContext());
task = new TimerTask() {
@Override
public void run() {
if (isAuto) {
handler.sendEmptyMessage(0);
}
}
};
//3秒执行一次task
timer.schedule(task, 1000, 3000);
}
private void startAuto() {
isAuto = true;
}
private void stopAuto() {
isAuto = false;
}
@Override
public void computeScroll() {
super.computeScroll();
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), 0);
invalidate();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//停止滚动
stopAuto();
//如果scroller还在滚动,则立即完成
if (!scroller.isFinished()) {
scroller.abortAnimation();
}
X = (int) event.getX();
break;
case MotionEvent.ACTION_UP:
startAuto();
int scrollX = getScrollX();
index = (scrollX + childWidth / 2) / childWidth;
//是否点击
if (isClick) {
onDiscoveryListener.imageOnClick(index);
} else {
if (index < 0) {
index = 0;
} else if (index > childrenCount - 1) {
index = childrenCount - 1;
}
int dx = index * childWidth - scrollX;
scroller.startScroll(scrollX, 0, dx, 0);
postInvalidate();
onDiscoveryListener.scrollImageIndex(index);
}
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) event.getX();
int dis = moveX - X;
scrollBy(-dis, 0);
X = moveX;
//设置不能点击
isClick = false;
break;
}
return true;
}
OnDiscoveryListener onDiscoveryListener;
public interface OnDiscoveryListener {
void imageOnClick(int position);
void scrollImageIndex(int position);
}
public void setOnDiscoveryListener(OnDiscoveryListener onDiscoveryListener) {
this.onDiscoveryListener = onDiscoveryListener;
}
复制代码
ok,现在轮播图已经完成了大部分了,接下来我们再添加指示器。我们先创建指示器背景,这里我们用drawable就可以。创建dot_normal.xml和dot_select.xml
dot_normal.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/white"/>
<size android:width="10dp" android:height="10dp"/>
</shape>
dot_select.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/holo_red_dark"/>
<size android:width="10dp" android:height="10dp"/>
</shape>
复制代码
然后新建DiscoveryFramLayout继承FrameLayout,添加轮播图DiscoveryViewGroup和指示器LinearLayout。
public class DiscoveryFramLayout extends FrameLayout implements DiscoveryViewGroup.OnDiscoveryListener {
DiscoveryViewGroup discoveryViewGroup;
LinearLayout dotLi;
public DiscoveryFramLayout(@NonNull Context context) {
super(context);
initDVP();
initDOT();
}
public DiscoveryFramLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initDVP();
initDOT();
}
public DiscoveryFramLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initDVP();
initDOT();
}
//初始化轮播图
private void initDVP() {
discoveryViewGroup = new DiscoveryViewGroup(getContext());
LayoutParams lp = new LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
discoveryViewGroup.setLayoutParams(lp);
discoveryViewGroup.setOnDiscoveryListener(this);
addView(discoveryViewGroup);
}
//初始化指示器LinearLayout
private void initDOT() {
dotLi = new LinearLayout(getContext());
LayoutParams lp = new LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 100);
lp.gravity = Gravity.BOTTOM;
dotLi.setLayoutParams(lp);
dotLi.setGravity(Gravity.CENTER);
dotLi.setOrientation(LinearLayout.HORIZONTAL);
dotLi.setAlpha(0.5f);
dotLi.setBackgroundColor(Color.GRAY);
addView(dotLi);
}
//对外暴露一个添加Bitmap数组
public void addBitmaps(List<Bitmap> list) {
for (int i = 0; i < list.size(); i++) {
ImageView imageView = new ImageView(getContext());
ViewGroup.LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
imageView.setLayoutParams(lp);
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
imageView.setImageBitmap(list.get(i));
discoveryViewGroup.addView(imageView);
ImageView dot = new ImageView(getContext());
LinearLayout.LayoutParams dlp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
dlp.setMargins(5, 5, 5, 5);
dot.setLayoutParams(dlp);
dot.setBackgroundResource(R.drawable.dot_normal);
dotLi.addView(dot);
}
}
@Override
public void imageOnClick(int position) {
onDiscoveryFramLayoutListener.onClick(position);
}
@Override
public void scrollImageIndex(int position) {
for (int i = 0; i < dotLi.getChildCount(); i++) {
ImageView dot = (ImageView) dotLi.getChildAt(i);
//判断当前图片的位置
if (i == position) {
dot.setBackgroundResource(R.drawable.dot_select);
} else {
dot.setBackgroundResource(R.drawable.dot_normal);
}
}
onDiscoveryFramLayoutListener.onScroll(position);
}
OnDiscoveryFramLayoutListener onDiscoveryFramLayoutListener;
public interface OnDiscoveryFramLayoutListener {
void onClick(int position);
void onScroll(int position);
}
public void setOnDiscoveryFramLayoutListener(OnDiscoveryFramLayoutListener onDiscoveryFramLayoutListener) {
this.onDiscoveryFramLayoutListener = onDiscoveryFramLayoutListener;
}
}
复制代码
现在我们可以在MainActivity的xml使用添加一个DiscoveryFramLayout, 并在MainActivity添加图片数组。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.imagebarnner.MainActivity">
<com.imagebarnner.ts.DiscoveryFramLayout
android:id="@+id/dis"
android:layout_width="match_parent"
android:layout_height="200dp"/>
</LinearLayout>
复制代码
MainActivity, MainActivity这里使用kotlin进行书写的。
class MainActivity : AppCompatActivity(), DiscoveryFramLayout.OnDiscoveryFramLayoutListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ids.map {
list.add(BitmapFactory.decodeResource(resources, it))
}
val dis = findViewById<DiscoveryFramLayout>(R.id.dis)
dis.setOnDiscoveryFramLayoutListener(this)
dis.addBitmaps(list)
}
val list = arrayListOf<Bitmap>()
val ids = listOf(R.mipmap.a1, R.mipmap.a2, R.mipmap.lo)
override fun onClick(position: Int) {
Toast.makeText(this, position.toString(), Toast.LENGTH_SHORT).show()
}
override fun onScroll(position: Int) {
}
}
复制代码
效果图
写的不好,请多提意见