android自定义浮动菜单,android 自定义LoopView实现循环菜单

一、可行性分析

LoopView是常见的循环View,一般应用于循环展示菜单项目,本次实现的是一组循环菜单,按照垂直方向,实际上,如果把某些变量互换,可以实现轮播图效果。

二、效果展示

b390c8a8e5b379b69917c2cf3b6321e4.gif

三、代码实现

public class LoopView extends View {

private static final int MAX_VISIBLE_COUNT = 5;

private TextPaint mTextPaint = null;

private DisplayMetrics displayMetrics = null;

private int mLineWidth = 1;

private int mTextSize = 24;

private int slopTouch = 0;

private int circleRadius;

private final List drawTasks = new ArrayList<>();

private final List cacheDrawTasks = new ArrayList<>();

private final List loopItems = new ArrayList<>();

boolean isInit = false;

public LoopView(Context context) {

this(context, null);

}

public LoopView(Context context, @Nullable AttributeSet attrs) {

this(context, attrs, 0);

}

public LoopView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

setClickable(true);

setFocusable(true);

setFocusableInTouchMode(true);

displayMetrics = context.getResources().getDisplayMetrics();

mTextPaint = createPaint();

slopTouch = ViewConfiguration.get(context).getScaledTouchSlop();

initDesignEidtor();

}

private void initDesignEidtor() {

if (!isInEditMode()) return;

int[] colors = {

Color.RED,

Color.CYAN,

Color.YELLOW,

Color.GRAY,

Color.GREEN,

Color.BLACK,

Color.MAGENTA,

0xffff9922,

};

String[] items = {

"新闻",

"科技",

"历史",

"军事",

"小说",

"娱乐",

"电影",

"电视剧",

};

for (int i = 0; i < items.length; i++) {

LoopItem loopItem = new LoopItem(items[i], colors[i % colors.length]);

loopItems.add(loopItem);

}

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

int widthMode = MeasureSpec.getMode(widthMeasureSpec);

int widthSize = MeasureSpec.getSize(widthMeasureSpec);

if (widthMode != MeasureSpec.EXACTLY) {

widthSize = displayMetrics.widthPixels;

}

int heightMode = MeasureSpec.getMode(heightMeasureSpec);

int heightSize = MeasureSpec.getSize(heightMeasureSpec);

if (heightMode != MeasureSpec.EXACTLY) {

heightSize = (int) (displayMetrics.widthPixels * 0.9f);

}

setMeasuredDimension(widthSize, heightSize);

}

private TextPaint createPaint() {

// 实例化画笔并打开抗锯齿

TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG);

paint.setAntiAlias(true);

paint.setStrokeWidth(dpTopx(mLineWidth));

paint.setTextSize(dpTopx(mTextSize));

return paint;

}

private float dpTopx(int dp) {

return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());

}

/**

* 基线到中线的距离=(Descent+Ascent)/2-Descent

* 注意,实际获取到的Ascent是负数。公式推导过程如下:

* 中线到BOTTOM的距离是(Descent+Ascent)/2,这个距离又等于Descent+中线到基线的距离,即(Descent+Ascent)/2=基线到中线的距离+Descent。

*/

public static float getTextPaintBaseline(Paint p) {

Paint.FontMetrics fontMetrics = p.getFontMetrics();

return (fontMetrics.descent - fontMetrics.ascent) / 2 - fontMetrics.descent;

}

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

circleRadius = Math.min(w / 2, h / 2) / MAX_VISIBLE_COUNT;

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

int width = getWidth();

int height = getHeight();

int id = canvas.save();

canvas.translate(width / 2, height / 2);

initCircle();

//前期重置,以便recycler复用

recyclerBefore(height);

//复用和移除

recycler();

//再次处理,防止view复用之后产生其他位移

recyclerAfter(height);

Collections.sort(drawTasks, new Comparator() {

@Override

public int compare(DrawTask left, DrawTask right) {

float dx = left.outOffset - right.outOffset;

if (dx > 0) {

return 1;

}

if (dx == 0) {

return 0;

}

return -1;

}

});

RectF contentRect = new RectF();

contentRect.left = -width / 2f;

contentRect.right = width / 2f;

contentRect.top = -circleRadius * (MAX_VISIBLE_COUNT - 1);

contentRect.bottom = circleRadius * (MAX_VISIBLE_COUNT - 1);

//canvas.clipRect(contentRect);

for (int i = 0; i < drawTasks.size(); i++) {

drawTasks.get(i).draw(canvas, mTextPaint);

}

drawGuideline(canvas, width);

canvas.restoreToCount(id);

}

private void recyclerAfter(int height) {

if (isTouchEventUp) {

float centerOffset = getNearlyOffsetY();

resetItemYOffset(height, centerOffset);

}else{

resetItemYOffset(height, 0);

}

}

private void recyclerBefore(int height) {

if (isTouchEventUp) {

float centerOffset = getNearlyOffsetY();

resetItemYOffset(height, centerOffset);

}else{

resetItemYOffset(height, offsetY);

}

isTouchEventUp = false;

}

private void resetItemYOffset(int height, float centerOffset) {

for (int i = 0; i < drawTasks.size(); i++) {

DrawTask task = drawTasks.get(i);

task.y = (task.y + centerOffset);

float ratio = Math.abs(task.y) / (height / 2);

if (ratio > 1f) {

ratio = 1f;

}

task.outOffset = ((10 + circleRadius) * 3 / 4f) * (1 - ratio);

}

}

private void drawGuideline(Canvas canvas, int width) {

if (!isInEditMode()) return;

mTextPaint.setColor(Color.BLACK);

mTextPaint.setStyle(Paint.Style.FILL);

int i = 0;

int counter = 0;

while (counter < MAX_VISIBLE_COUNT) {

float topY = i * 2 * circleRadius;

RectF guideRect = new RectF();

guideRect.left = -width / 2f;

guideRect.right = width / 2f;

guideRect.top = topY - 0.5f;

guideRect.bottom = topY + 0.5f;

canvas.drawRect(guideRect, mTextPaint);

counter++;

float bottomY = -i * 2 * circleRadius;

if (topY == bottomY) {

i++;

continue;

}

guideRect.top = bottomY - 0.5f;

guideRect.bottom = bottomY + 0.5f;

canvas.drawRect(guideRect, mTextPaint);

counter++;

i++;

}

}

private void recycler() {

if (drawTasks.size() < (MAX_VISIBLE_COUNT - 2)) return;

Collections.sort(drawTasks, new Comparator() {

@Override

public int compare(DrawTask left, DrawTask right) {

float dx = left.y - right.y;

if (dx > 0) {

return 1;

}

if (dx == 0) {

return 0;

}

return -1;

}

});

DrawTask head = drawTasks.get(0);

DrawTask tail = drawTasks.get(drawTasks.size() - 1);

int height = getHeight();

if (head.y < -height / 2 - circleRadius) {

drawTasks.remove(head);

addToCachePool(head);

head.setLoopItem(null);

} else {

DrawTask drawTask = getCachePool();

LoopItem loopItem = head.getLoopItem();

LoopItem preLoopItem = getPreLoopItem(loopItem);

drawTask.setLoopItem(preLoopItem);

drawTask.y = head.y - circleRadius * 2;

drawTasks.add(0, drawTask);

}

if (tail.y > height / 2 + circleRadius) {

drawTasks.remove(tail);

addToCachePool(tail);

tail.setLoopItem(null);

} else {

DrawTask drawTask = getCachePool();

LoopItem loopItem = tail.getLoopItem();

LoopItem nextLoopItem = getNextLoopItem(loopItem);

drawTask.setLoopItem(nextLoopItem);

drawTask.y = tail.y + circleRadius * 2;

drawTasks.add(drawTask);

}

}

private LoopItem getNextLoopItem(LoopItem loopItem) {

int index = loopItems.indexOf(loopItem);

if (index < loopItems.size() - 1) {

return loopItems.get(index + 1);

}

return loopItems.get(0);

}

private LoopItem getPreLoopItem(LoopItem loopItem) {

int index = loopItems.indexOf(loopItem);

if (index > 0) {

return loopItems.get(index - 1);

}

return loopItems.get(loopItems.size() - 1);

}

private DrawTask getCachePool() {

if (cacheDrawTasks.size() > 0) {

return cacheDrawTasks.remove(0);

}

DrawTask drawTask = createDrawTask();

return drawTask;

}

private void addToCachePool(DrawTask top) {

cacheDrawTasks.add(top);

}

private void initCircle() {

if (isInit) {

return;

}

isInit = true;

List drawTasklist = new ArrayList<>();

int i = 0;

while (drawTasklist.size() < MAX_VISIBLE_COUNT) {

float topY = i * 2 * circleRadius;

DrawTask drawTask = new DrawTask(0, topY, circleRadius, 0);

drawTasklist.add(drawTask);

float bottomY = -i * 2 * circleRadius;

if (topY == bottomY) {

i++;

continue;

}

drawTask = new DrawTask(0, bottomY, circleRadius, 0);

drawTasklist.add(drawTask);

i++;

}

Collections.sort(drawTasklist, new Comparator() {

@Override

public int compare(DrawTask left, DrawTask right) {

float dx = left.y - right.y;

if (dx > 0) {

return 1;

}

if (dx == 0) {

return 0;

}

return -1;

}

});

drawTasks.clear();

if (loopItems.size() == 0) return;

for (int j = 0; j < drawTasklist.size(); j++) {

drawTasklist.get(j).setLoopItem(loopItems.get(j % loopItems.size()));

}

drawTasks.addAll(drawTasklist);

}

private DrawTask createDrawTask() {

DrawTask drawTask = new DrawTask(0, 0, circleRadius, 0);

return drawTask;

}

private float startEventX = 0;

private float startEventY = 0;

private boolean isTouchMove = false;

private float offsetScrollY = 0;

private float offsetY = 0;

boolean isTouchEventUp = false;

@Override

public boolean onTouchEvent(MotionEvent event) {

int action = event.getActionMasked();

isTouchEventUp = false;

switch (action) {

case MotionEvent.ACTION_DOWN:

offsetY = 0;

startEventX = event.getX() - getWidth() / 2;

startEventY = event.getY() - getHeight() / 2;

break;

case MotionEvent.ACTION_MOVE:

float currentX = event.getX() - getWidth() / 2;

float currentY = event.getY() - getHeight() / 2;

float dx = currentX - startEventX;

float dy = currentY - startEventY;

if (Math.abs(dx) < Math.abs(dy) && Math.abs(dy) >= slopTouch) {

isTouchMove = true;

}

if (!isTouchMove) {

break;

}

offsetY = dy;

offsetScrollY = (offsetScrollY + dy);

startEventX = currentX;

startEventY = currentY;

postInvalidate();

return true;

case MotionEvent.ACTION_CANCEL:

case MotionEvent.ACTION_OUTSIDE:

case MotionEvent.ACTION_UP:

isTouchMove = false;

isTouchEventUp = true;

offsetY = 0;

Log.d("eventup", "offsetY=" + offsetY);

invalidate();

break;

}

return super.onTouchEvent(event);

}

private float getNearlyOffsetY() {

float minY = 0;

float outOffset = 0;

for (int i = 0; i < drawTasks.size(); i++) {

DrawTask drawTask = drawTasks.get(i);

if (Math.abs(drawTask.outOffset) > outOffset) {

minY = -drawTask.y;

outOffset = drawTask.outOffset;

}

}

Log.d("eventup", "------>" + minY);

return minY;

}

public void setLoopItems(List loopItems) {

this.loopItems.clear();

this.drawTasks.clear();

this.cacheDrawTasks.clear();

this.isInit = false;

if (loopItems != null) {

this.loopItems.addAll(loopItems);

}

postInvalidate();

}

public static class DrawTask {

private T loopItem;

private float radius;

private float x;

private float y;

private float outOffset;

public DrawTask(float x, float y, int radius, float outOffset) {

this.radius = radius;

this.x = x;

this.y = y;

this.outOffset = outOffset;

}

public void setLoopItem(T loopItem) {

this.loopItem = loopItem;

}

public void draw(Canvas canvas, TextPaint textPaint) {

if (loopItem == null) return;

textPaint.setColor(loopItem.getColor());

textPaint.setStyle(Paint.Style.FILL);

canvas.drawCircle(x, y, radius + outOffset, textPaint);

textPaint.setColor(Color.WHITE);

textPaint.setStyle(Paint.Style.FILL);

String text = loopItem.getText();

float textWidth = textPaint.measureText(text);

float baseline = getTextPaintBaseline(textPaint);

canvas.drawText(text, -textWidth / 2, y + baseline, textPaint);

}

public T getLoopItem() {

return loopItem;

}

}

public static class LoopItem implements Serializable {

private int color;

private String text;

public LoopItem(String text, int color) {

this.color = color;

this.text = text;

}

public int getColor() {

return color;

}

public void setColor(int color) {

this.color = color;

}

public String getText() {

return text;

}

public void setText(String text) {

this.text = text;

}

}

}

四、应用

LoopView loopView = findViewById(R.id.loopviews);

final List loopItems = new ArrayList<>();

int[] colors = {

Color.RED,

Color.CYAN,

Color.GRAY,

Color.GREEN,

Color.BLACK,

Color.MAGENTA,

0xffff9922,

0xffFF4081,

0xffFFEAC4

};

String[] items = {

"新闻",

"科技",

"历史",

"军事",

"小说",

"娱乐",

"电影",

"电视剧",

};

for (int i = 0; i < items.length; i++) {

LoopView.LoopItem loopItem = new LoopView.LoopItem(items[i], colors[i % colors.length]);

loopItems.add(loopItem);

}

loopView.setLoopItems(loopItems);

}

LoopView 是一个强大的轮转大图控件,并且提供了许多配置方法来达到您的显示效果和需求。github地址:https://github.com/xuehuayous/Android-LoopView介绍博客地址:http://blog.csdn.net/xuehuayous/article/details/50518393在项目中使用 LoopView如果您的项目使用 Gradle 构建, 只需要在您的build.gradle文件添加下面一行到 dependencies :    compile 'com.kevin:loopview:1.0.5'简单使用在layout.xml 中配置LoopView在Layout文件添加<com.kevin.loopview.AdLoopView<com.kevin.loopview.AdLoopView     android:id="@ id/main_act_adloopview"     android:layout_width="match_parent"     android:layout_height="192dp"> </com.kevin.loopview.AdLoopView>在代码中配置AdLoopView mLoopView = (AdLoopView) this.findViewById(R.id.main_act_adloopview); String json = LocalFileUtils.getStringFormAsset(this, "loopview_date.json"); // 使用 JsonTool 封装 JSON 数据到实体对象 LoopData loopData = JsonTool.toBean(json, LoopData.class); // 通过对象的方式设置数据 mLoopView.refreshData(loopData); // 开始轮转 mLoopView.startAutoLoop(); // 设置点击监听 mLoopView.setOnClickListener(new BaseLoopAdapter.OnItemClickListener() {         @Override         public void onItemClick(PagerAdapter parent, View view,              int position, int realPosition) {             // 获取数据             LoopData loopData = mLoopView.getLoopData();             String url = loopData.items.get(position).link;             // 通过系统浏览器打开跳转链接             Intent intent = new Intent();             intent.setData(Uri.parse(url));             intent.setAction(Intent.ACTION_VIEW);             startActivity(intent);         }     });更多配置XML 配置在XML中使用AdLoopView,可以有如下配置:<com.kevin.loopview.AdLoopView     android:id="@ id/adloop_act_adloopview"     android:layout_width="match_parent"     android:layout_height="192dp"     kevin:loop_interval="5000"     kevin:loop_dotMargin="5dp"     kevin:loop_autoLoop="[true|false]"     kevin:loop_dotSelector="@drawable/ad_dots_selector"     kevin:loop_layoutId="@layout/ad_loopview_layout"> </com.kevin.loopview.AdLoopView>在代码中配置// 设置ViewPager页面切换时间 mLoopView.setScrollDuration(1000); // 设置轮转时间间隔 mLoopView.setInterval(3000); // 以集合的方式初始化数据 mLoopView.setLoopViewPager(List<Map<String, String>> data); // 以JSON的方式初始化数据 mLoopView.setLoopViewPager(String jsonData); // 以数据实体的方式初始化数据 mLoopView.setLoopViewPager(LoopData rotateData); // 以集合的方式刷新数据 mLoopView.refreshData(final List<Map<String, String>> data); // 以数据实体的方式刷新数据 mLoopView.refreshData(LoopData loopData); // 以JSON的方式刷新数据 mLoopView.refreshData(String jsonData); // 获取配置的轮转大图数据 mLoopView.getLoopData(); // 开始自动轮转 mLoopView.startAutoLoop(); // 在指定时间延迟后自动轮转 mLoopView.startAutoLoop(long delayTimeInMills); // 停止自动轮转 mLoopView.stopAutoLoop(); // 设置自定义布局 mLoopView.setLoopLayout(int layoutResId);
Android 悬浮窗菜单,可在launcher或app中使用。示例代码:@Override public void onCreate() {     super.onCreate();     mFloatMenu = new FloatMenu.Builder(this)             .floatLoader(R.drawable.yw_anim_background)             .floatLogo(R.drawable.yw_image_float_logo)             .addMenuItem(android.R.color.transparent, R.drawable.yw_menu_account, Const.MENU_ITEMS[0], android.R.color.black, this)             .addMenuItem(android.R.color.transparent, R.drawable.yw_menu_favour, Const.MENU_ITEMS[1], android.R.color.black, this)             .addMenuItem(android.R.color.transparent, R.drawable.yw_menu_fb, Const.MENU_ITEMS[2], android.R.color.black, this)             .addMenuItem(android.R.color.transparent, R.drawable.yw_menu_msg, Const.MENU_ITEMS[3], android.R.color.black, this)             .addMenuItem(android.R.color.transparent, R.drawable.yw_menu_close, Const.MENU_ITEMS[4], android.R.color.black, this)             .menuBackground(R.drawable.yw_menu_bg)             .onMenuActionListner(this)             .build();     mFloatMenu.show(); } public void showFloat() {     if (mFloatMenu != null)         mFloatMenu.show(); } public void hideFloat() {     if (mFloatMenu != null) {         mFloatMenu.hide();     } } public void destroyFloat() {     hideFloat();     if (mFloatMenu != null) {         mFloatMenu.destroy();     }     mFloatMenu = null; }  private void showRed() {     if (!hasNewMsg) {         mFloatMenu.changeLogo(R.drawable.yw_image_float_logo, R.drawable.yw_menu_msg, 3);     } else {         mFloatMenu.changeLogo(R.drawable.yw_image_float_logo_red, R.drawable.yw_menu_msg_red, 3);     } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值