上次工程要做轮播图,于是呢,百度了一下,看到大家都是用ViewPager去实现轮播图,有几点就是感觉怪怪的,比如说,他们一般写getCount都是返回的Integer.MAX_VALUE;那么我就去查了一下,这个东西的值为 0x7fffffff(转换成十进制2147483647),换一种说法说,这种做法做的轮播图根本没有实现无限循环,只是说把count设置成一个足够大的数字,一般人根本不可能滑倒这么多,当然这样做实现没有问题,就是感觉怪异。于是呢我自己就动手做了一个,通过复写FrameLayout来实现的轮播图。顺手联想一下自定义View,以及属性动画相关的内容。
贴一张图片:
其实我这么做的原理也很简单,就是在Adapter中,获取三个View,然后显示中间的那个,前面和后面的影藏,然后当手指滑动的时候,从下一个界面和当前页面获取到一直bitmap的图片,在当前View的上层覆盖一个View,然后通过滑动的距离,计算两张图片显示的位置,当手指松开的时候,进行一段动画,完成动画之后,切换重新装载View。完成轮播。
package com.example.qmuitest.Banner;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Adapter;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.example.qmuitest.R;
import java.util.Timer;
import java.util.TimerTask;
public class BannerView extends FrameLayout implements OnAnimationViewListener{
private Adapter adapter;
private int current_position = 0;
private float mStartX,mStartY,mEndX,mEndY,mMoveX,mMoveY;
int indicator_size = 10,indicator_space = 40;
private AdapterDataSetObserver dataSetObserver;
private float percent = 0 ;
private View current_view = null, front_view = null, next_view = null;
private AnimationView animationView;
private LayoutParams mLayoutParams;
private Boolean isSlide = false;
private int second = 0;
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
second +=1;
if (adapter != null && adapter.getCount() > 0 && second > 10) {
createAnimationView();
if (!animationView.isAnimationRunning() && !isSlide) {
switchAniamtionBitmap(-1);
if (!isAnimationViewVisible()) {
setAnimationViewVisible(true);
}
animationView.startAnimation(-1);
}
second = 0;
}
}
super.handleMessage(msg);
}
};
Timer timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
handler.sendEmptyMessage(1);
}
};
public BannerView( Context context) {
this(context,null);
}
public BannerView( Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public BannerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
public int dip2px(float dpValue) {
final float scale = getContext().getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
private void init( AttributeSet attrs){
mLayoutParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.BannerView);
indicator_size = ta.getInt(R.styleable.BannerView_indicator_size, dip2px(4));
indicator_space = ta.getInt(R.styleable.BannerView_indicator_space, dip2px(15));
ta.recycle();
timer.schedule(timerTask, 1000, 1000);
}
public void setAdapter(Adapter adapter) {
if (adapter != null && dataSetObserver != null) {
adapter.unregisterDataSetObserver(dataSetObserver);
}
this.adapter = adapter;
dataSetObserver = new AdapterDataSetObserver();
adapter.registerDataSetObserver(dataSetObserver);
updateView();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (getWidth() <= 0 || getHeight() <= 0) {
return false;
}
if (adapter == null || adapter.getCount() == 0) {
return true;
}
//当动画组件动画执行中,则忽略touch事件
if (animationView != null && animationView.isAnimationRunning()) {
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartX = event.getX();
mStartY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
mMoveX = event.getX() - mStartX;
createAnimationView();
isSlide = true;
percent = mMoveX / getWidth();
if (percent < -1) {
percent = -1;
} else if (percent > 1) {
percent = 1;
}
if (!isAnimationViewVisible()) {
setAnimationViewVisible(true);
}
switchAniamtionBitmap(percent);
animationView.setAnimationPercent(percent);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_OUTSIDE:
float toPercent = 0;
if(percent > 0.1f){
toPercent = 1;
}else if(percent < -0.1f) {
toPercent = -1;
}
animationView.startAnimation(toPercent);
break;
}
return true;
}
private void createAnimationView(){
if(animationView == null){
animationView = new AnimationView(getContext());
animationView.setOnAnimationViewListener(this);
}
}
protected boolean isAnimationViewVisible() {
return animationView != null && ((View) animationView).getParent() != null;
}
private void switchAniamtionBitmap(float percent){
//当第一次滑动或改变方向的时候重新装载图片
if(animationView.direction == 0 || animationView.direction * percent < 0){
Bitmap frontBitmap = getViewBitmap(current_view);
Bitmap backBitmap = percent > 0? getViewBitmap(front_view):getViewBitmap(next_view);
initAniamtionView(frontBitmap, backBitmap);
}
}
/*
* 获取view截图
*/
protected Bitmap getViewBitmap(View view) {
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
return view.getDrawingCache();
}
private void initAniamtionView(Bitmap frontBitmap, Bitmap backBitmap) {
animationView.setBitmap(frontBitmap, backBitmap);
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStrokeWidth(3.0f);
paint.setColor(Color.parseColor("#45bec6"));
paint.setStyle(Paint.Style.STROKE);
int count = adapter.getCount();
int x_space = (getWidth() - (count - 1) * indicator_space)/2;
for(int i = 0 ; i <count ;i ++){
if(current_position == i){
paint.setStyle(Paint.Style.FILL_AND_STROKE);
}else {
paint.setStyle(Paint.Style.STROKE);
}
canvas.drawCircle(x_space + i * indicator_space,getHeight() - indicator_space ,indicator_size,paint);
}
}
protected void setAnimationViewVisible(boolean visible) {
if (animationView == null) {
return;
}
if (visible) {
addView((View) animationView, mLayoutParams);
} else {
removeView((View) animationView);
animationView.direction = 0;
}
}
// 更新界面
private void updateView(){
removeAllViews();
setAnimationViewVisible(false);
if(adapter.getCount()>0){
if(current_position>=adapter.getCount()){
current_position = 0;
}
if(current_position<0){
current_position = adapter.getCount() -1;
}
current_view = adapter.getView(current_position,current_view,null);
front_view = adapter.getView(getPosition(current_position-1),front_view,null);
next_view = adapter.getView(getPosition(current_position+1),next_view,null);
addView(front_view);
addView(current_view);
addView(next_view);
front_view.setVisibility(INVISIBLE);
current_view.setVisibility(VISIBLE);
next_view.setVisibility(INVISIBLE);
}
}
private int getPosition(int position){
if(position>=adapter.getCount()){
position = 0;
}
if(position < 0){
position = adapter.getCount() -1;
}
return position;
}
@Override
public void pageChanged(float toPercent) {
Log.e("leafly",String.valueOf(percent));
if(toPercent == 1){
current_position -=1;
}else if(toPercent == -1){
current_position +=1;
}
updateView();
isSlide = false;
second = 0;
percent = 0;
}
public class AdapterDataSetObserver extends DataSetObserver{
@Override
public void onChanged() {
super.onChanged();
updateView();
}
@Override
public void onInvalidated() {
super.onInvalidated();
}
}
@Override
protected void onDetachedFromWindow()
{
super.onDetachedFromWindow();
timer.cancel();
}
}
package com.example.qmuitest.Banner;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import androidx.annotation.Nullable;
public class AnimationView extends View {
public float direction = 0;
public float percent;
Paint paint = new Paint();
private ValueAnimator mAnimator;
private OnAnimationViewListener onAnimationViewListener;
private Bitmap frontBitmap = null, backBitmap = null;
public AnimationView(Context context) {
this(context,null);
}
public AnimationView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public AnimationView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.parseColor("#29cf79"));
paint.setStrokeWidth(4f);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
}
public void setOnAnimationViewListener(OnAnimationViewListener onAnimationViewListener) {
this.onAnimationViewListener = onAnimationViewListener;
}
public void setBitmap(Bitmap frontBitmap, Bitmap backBitmap){
this.frontBitmap = frontBitmap;
this.backBitmap = backBitmap;
}
public void setAnimationPercent(float percent){
this.percent = percent;
if(percent>0){
direction = 1;
}else if(percent < 0){
direction = -1;
}
postInvalidate();
}
public boolean isAnimationRunning() {
if (mAnimator == null) {
return false;
}
return mAnimator.isRunning();
}
public void startAnimation(float toPercent) {
if (mAnimator != null && mAnimator.isRunning()) {
return;
}
mAnimator = ValueAnimator.ofFloat(percent, toPercent);
mAnimator.setDuration((long) (Math.abs(toPercent - percent) * 300));
mAnimator.setInterpolator(new DecelerateInterpolator());
mAnimator.start();
OnAnimationListener onAnimationListener = new OnAnimationListener(toPercent);
mAnimator.addUpdateListener(onAnimationListener);
mAnimator.addListener(onAnimationListener);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
//canvas.drawCircle(width * percent,50,10,paint);
if(frontBitmap == null || backBitmap == null){
return;
}
canvas.drawBitmap(frontBitmap,width * percent,0,paint);
if(percent!=0){
canvas.drawBitmap(backBitmap,width * (percent > 0 ? percent -1:percent+1),0,paint);
}
//canvas.drawCircle(100,100,50,paint);
}
class OnAnimationListener implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
private float toPercent;
public OnAnimationListener(float toPercent) {
this.toPercent = toPercent;
}
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
percent = 0;
if(onAnimationViewListener==null){
return;
}
onAnimationViewListener.pageChanged(toPercent);
}
@Override
public void onAnimationCancel(Animator animation) {
percent = 0;
}
@Override
public void onAnimationRepeat(Animator animation) {
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setAnimationPercent((float) animation.getAnimatedValue());
}
}
}
package com.example.qmuitest.Banner;
/**
* @author leafly
* @description
* @date 2019/6/4 16:55
*/
public interface OnAnimationViewListener {
public void pageChanged(float toPrecent);
}
<declare-styleable name="BannerView">
<attr name="indicator_size" format="dimension"/>
<attr name="indicator_space" format="dimension"/>
<attr name="auto_time" format="integer"/>
</declare-styleable>
代码写完了,然后也调用一下,
先写Adapter
package com.example.qmuitest.Banner;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.example.qmuitest.R;
import com.example.qmuitest.Selector.SelectorAdapter;
import com.qmuiteam.qmui.widget.section.QMUIStickySectionAdapter;
import java.util.ArrayList;
import java.util.List;
public class BannerAdapter extends BaseAdapter {
String[] pic_urls = new String[]{
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1591008936948&di=b0f06aea0614b7e40bd677208023cecf&imgtype=0&src=http%3A%2F%2Fc.hiphotos.baidu.com%2Fzhidao%2Fpic%2Fitem%2Fd000baa1cd11728bcdde8185ccfcc3cec2fd2ca1.jpg",
"https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2112376104,3802761195&fm=26&gp=0.jpg",
"https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1760283799,1689150510&fm=26&gp=0.jpg",
};
private Context context;
public BannerAdapter(Context context) {
this.context = context;
}
public void setPic_urls(String[] pic_urls) {
this.pic_urls = pic_urls;
}
public Context getContext() {
return context;
}
@Override
public int getCount() {
return pic_urls.length;
}
@Override
public Object getItem(int position) {
return pic_urls[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ViewHolder viewHolder;
if(convertView == null){
view = LayoutInflater.from(getContext()).inflate(R.layout.banner_item_layout,parent,false);
viewHolder = new ViewHolder();
viewHolder.imageView = view.findViewById(R.id.iv_picture);
view.setTag(viewHolder);
}else {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}
Glide.with(getContext()).load(pic_urls[position]).into(viewHolder.imageView);
return view;
}
public class ViewHolder{
public ImageView imageView;
}
}
Adapter的布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:orientation="horizontal"
android:background="@drawable/banner1_3"
android:gravity="center_vertical"
android:layout_width="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/iv_picture"
android:scaleType="centerCrop" />
</LinearLayout>
然后再在activity中调用一下,就ok了
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".TestActivity">
<com.example.qmuitest.Banner.BannerView
android:layout_width="match_parent"
app:layout_constraintTop_toTopOf="parent"
android:background="@color/colorAccent"
android:id="@+id/bv_test"
android:layout_height="200dp">
</com.example.qmuitest.Banner.BannerView>
</androidx.constraintlayout.widget.ConstraintLayout>
bannerAdapter = new BannerAdapter(TestActivity.this);
BannerView bannerView = findViewById(R.id.bv_test);
bannerView.setAdapter(bannerAdapter);