本文主要是介绍地球仪式分布的控件,球体控件,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
效果:
本人爱好做android各种好玩的效果和交互,欢迎大家交流。
引用大神的成果
http://www.open-open.com/lib/view/open1455706317480.html
原来的效果是这样的
虽然我添加了3D翻转,还有光照背景,看起来华丽,但其实不知道在什么地方用得上。又修改了代码,添加了滑动停止后,自动锁定最靠近中央的item,作为一个选择菜单使用,跟日期选择控件有点类似。
给这个控件的每个item添加触发点击事件,应该是不太需要的吧。
毕竟,每个都要点击,然后触发比如查看大图之类,还不如用最直观的gridview呢。
代码:
从大神的工程的基础上重构了实体类Tag.java的一些属性名字,比如用简单的x,y,z表示子控件的3d空间坐标
public class Tag {private int popularity; //this is the importance/popularity of the Tagpublic float x, y, z; //the center of the 3D Tagprivate float loc2DX, loc2DY;private float scale;private float[] argb;private static final int DEFAULT_POPULARITY = 5;
修改了TagCloudView.java。
去掉onLayout方法,改沿用继承FrameLayout了,修改updateChild方法,view的位置,大小,透明度,X,Y旋转计算直接完成,然后invalidate()重新绘制。
修改onTouchEvent,不再根据触摸点离中心有多远,而是根据手指滑动距离,决定X,Y方向的角度偏转修改对惯性摩擦阻力的一些参数(run方法),由于修改了角度偏转方法,如果还按原来的参数,则阻力太小,根本停不下来
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;import com.dxtx.common.R;/*** Copyright © 2016 moxun* <p/>* Permission is hereby granted, free of charge, to any person obtaining* a copy of this software and associated documentation files (the “Software”),* to deal in the Software without restriction, including without limitation the* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or* sell copies of the Software, and to permit persons to whom the Software is* furnished to do so, subject to the following conditions:* <p/>* The above copyright notice and this permission notice shall be* included in all copies or substantial portions of the Software.* <p/>* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE* OR OTHER DEALINGS IN THE SOFTWARE.*/public class TagCloudView extends FrameLayout implements Runnable, TagsAdapter.OnDataSetChangeListener {// private final float TOUCH_SCALE_FACTOR = .8f;private final float TOUCH_SCALE_FACTOR = 12f;private final float TRACKBALL_SCALE_FACTOR = 10;private float tspeed = 2f;private TagCloud mTagCloud;private float mAngleX = 0.5f;private float mAngleY = 0.5f;private float centerX, centerY;private float radius;private float radiusPercent = 0.9f;private float[] darkColor = new float[]{1f, 0f, 0f, 1};private float[] lightColor = new float[]{0.9412f, 0.7686f, 0.2f, 1};private int sphereColor = Color.WHITE;private State touch_state = State.Scroll;private Tag selectTag;private int selectIndex;private float minLockError;private enum State {Touch, Calm, AutoLock, Scroll}private boolean autoSelect = false;private Handler handler = new Handler(Looper.getMainLooper());private TagsAdapter tagsAdapter;Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);private Paint glancePaint;private RectF glanceRect = new RectF();private RectF shadowRect = new RectF();private Bitmap glanceBitmap, shadowBitmap;public TagCloudView(Context context) {super(context);setFocusableInTouchMode(true);init(context, null);}public TagCloudView(Context context, AttributeSet attrs) {super(context, attrs);setFocusableInTouchMode(true);init(context, attrs);}public TagCloudView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);setFocusableInTouchMode(true);init(context, attrs);}private void init(Context context, AttributeSet attrs) {mTagCloud = new TagCloud();if (attrs != null) {TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TagCloudView);int light = typedArray.getColor(R.styleable.TagCloudView_lightColor, Color.GREEN);setLightColor(light);int dark = typedArray.getColor(R.styleable.TagCloudView_darkColor, Color.GRAY);setDarkColor(dark);sphereColor = typedArray.getColor(R.styleable.TagCloudView_sphereColor, Color.WHITE);autoSelect = typedArray.getBoolean(R.styleable.TagCloudView_autoLockSelect, autoSelect);float p = typedArray.getFloat(R.styleable.TagCloudView_radiusPercent, radiusPercent);setRadiusPercent(p);float s = typedArray.getFloat(R.styleable.TagCloudView_scrollSpeed, 2f);setScrollSpeed(s);}paint.setColor(sphereColor);glancePaint = new Paint(Paint.ANTI_ALIAS_FLAG);glancePaint.setDither(true);glancePaint.setFilterBitmap(true);glancePaint.setAlpha(200);glanceBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ball_light);shadowBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.shadow);minLockError = getResources().getDimension(R.dimen._1dp);}public final void setAdapter(TagsAdapter adapter) {tagsAdapter = adapter;tagsAdapter.setOnDataSetChangeListener(this);onChange();getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {getViewTreeObserver().removeGlobalOnLayoutListener(this);//设置每个item的旋转中心for (int i = 0; i < getChildCount(); i++) {View child = getChildAt(i);child.setPivotX(child.getMeasuredWidth() / 2);child.setPivotY(child.getMeasuredHeight() / 2);}updateChild();}});}public void setLightColor(int color) {float[] argb = new float[4];argb[0] = Color.alpha(color) / 1.0f / 255;argb[1] = Color.red(color) / 1.0f / 255;argb[2] = Color.green(color) / 1.0f / 255;argb[3] = Color.blue(color) / 1.0f / 255;lightColor = argb.clone();onChange();}public void setDarkColor(int color) {float[] argb = new float[4];argb[0] = Color.alpha(color) / 1.0f / 255;argb[1] = Color.red(color) / 1.0f / 255;argb[2] = Color.green(color) / 1.0f / 255;argb[3] = Color.blue(color) / 1.0f / 255;darkColor = argb.clone();onChange();}public void setRadiusPercent(float percent) {if (percent > 1f || percent < 0f) {throw new IllegalArgumentException("percent value not in range 0 to 1");} else {radiusPercent = percent;onChange();}}private void initFromAdapter() {this.postDelayed(new Runnable() {@Overridepublic void run() {mTagCloud.setRadius((int) radius);mTagCloud.setTagColorLight(lightColor);//higher colormTagCloud.setTagColorDark(darkColor);//lower colormTagCloud.clear();removeAllViews();for (int i = 0; i < tagsAdapter.getCount(); i++) {TagCloudView.this.mTagCloud.add(new Tag(tagsAdapter.getPopularity(i)));addView(tagsAdapter.getView(getContext(), i, TagCloudView.this));}mTagCloud.create(true);mTagCloud.setAngleX(mAngleX);mTagCloud.setAngleY(mAngleY);mTagCloud.update();}}, 0);}public void setScrollSpeed(float scrollSpeed) {tspeed = scrollSpeed;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int modeW = MeasureSpec.getMode(widthMeasureSpec);int modeH = MeasureSpec.getMode(heightMeasureSpec);if (modeW == MeasureSpec.EXACTLY) {heightMeasureSpec = widthMeasureSpec;} else if (modeH == MeasureSpec.EXACTLY) {widthMeasureSpec = heightMeasureSpec;}//宽高都wrap_content,指定最大值,则用最大值else if (modeW == MeasureSpec.AT_MOST && modeH == MeasureSpec.AT_MOST) {int size = Math.min(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(widthMeasureSpec));widthMeasureSpec = heightMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);} else if (modeW == MeasureSpec.AT_MOST) {int size = MeasureSpec.getSize(widthMeasureSpec);widthMeasureSpec = heightMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);} else if (modeH == MeasureSpec.AT_MOST) {int size = MeasureSpec.getSize(heightMeasureSpec);widthMeasureSpec = heightMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);}//如果父控件让我宽高都随意,则我也随意else {widthMeasureSpec = heightMeasureSpec = MeasureSpec.makeMeasureSpec(600, MeasureSpec.EXACTLY);}super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();handler.post(this);}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();handler.removeCallbacksAndMessages(null);}private void updateChild() {for (int i = 0; i < getChildCount(); i++) {View child = getChildAt(i);if (child.getVisibility() != GONE) {Tag tag = mTagCloud.get(i);float scale = tag.getScale();child.setScaleX(scale);child.setScaleY(scale);if (scale < 1 && scale > 0.9f) {//[0.9,1]短暂区间的强制转为区间[0.1,1]scale = 9 * scale - 8;} else if (scale <= 0.9f) {scale = 0.1f;//让转到背面的看起来模糊,}child.setAlpha(scale);int left, top;left = (int) (centerX + tag.x - child.getMeasuredWidth() / 2f);top = (int) (centerY + tag.y - child.getMeasuredHeight() / 2f);child.setX(left);child.setY(top);double rx = Math.sqrt(radius * radius - tag.x * tag.x);float rotationX = -(float) (Math.asin(tag.y / rx) * 180 / Math.PI);float rotationY = -(float) (Math.asin(-tag.x / radius) * 180 / Math.PI);child.setRotationX(rotationX);child.setRotationY(rotationY);}}invalidate();}public void reset() {mTagCloud.reset();updateChild();}@Overridepublic boolean onTrackballEvent(MotionEvent e) {float x = e.getX();float y = e.getY();mAngleX = (y) * tspeed * TRACKBALL_SCALE_FACTOR;mAngleY = (-x) * tspeed * TRACKBALL_SCALE_FACTOR;mTagCloud.setAngleX(mAngleX);mTagCloud.setAngleY(mAngleY);mTagCloud.update();updateChild();return true;}float x0, y0, downX, downY;@Overridepublic boolean dispatchTouchEvent(MotionEvent e) {super.dispatchTouchEvent(e);float x = e.getX();float y = e.getY();switch (e.getAction()) {case MotionEvent.ACTION_DOWN:touch_state = State.Touch;x0 = x;y0 = y;break;case MotionEvent.ACTION_MOVE://rotate elements depending on how far the selection point is from center of cloud
// float dx = x - centerX;
// float dy = y - centerY;if (selectTag != null) {selectTag = null;getChildAt(selectIndex).setSelected(false);}float dx = x - x0;float dy = y - y0;x0 = x;y0 = y;mAngleX = (dy / radius) * tspeed * TOUCH_SCALE_FACTOR;mAngleY = (-dx / radius) * tspeed * TOUCH_SCALE_FACTOR;processTouch();break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL://开始惯性滚动touch_state = State.Scroll;break;}return true;}private boolean intercept = false;@Overridepublic boolean onInterceptTouchEvent(MotionEvent e) {float x = e.getX();float y = e.getY();switch (e.getAction()) {case MotionEvent.ACTION_DOWN:
// touch_state = State.Touch;downX = x;downY = y;break;case MotionEvent.ACTION_MOVE:if (intercept) {return true;}float dx = x - downX;float dy = y - downY;if (Math.sqrt(dx * dx + dy * dy) > 100) {//滑动超过一定距离,拦截事件return intercept = true;}break;case MotionEvent.ACTION_UP:boolean temp = intercept;intercept = false;return temp;}return false;}private void processTouch() {if (mTagCloud != null) {mTagCloud.setAngleX(mAngleX);mTagCloud.setAngleY(mAngleY);mTagCloud.update();}updateChild();}@Overridepublic void onChange() {if (!isInEditMode())initFromAdapter();}@Overridepublic void run() {if (touch_state == State.Scroll) {//是否已经停止滚动boolean stop = mAngleX == 0 && mAngleY == 0;if (mAngleX > 0.4f) {mAngleX -= 0.1f;} else if (mAngleX < -0.4f) {mAngleX += 0.1f;} else {mAngleX = 0;}if (mAngleY > 0.4f) {mAngleY -= 0.1f;} else if (mAngleY < -0.4f) {mAngleY += 0.1f;} else {mAngleY = 0;}if (autoSelect && !stop && mAngleX == 0 && mAngleY == 0) {startAutoLockItem();}processTouch();} else if (touch_state == State.AutoLock) {float absX = Math.abs(selectTag.x);float absY = Math.abs(selectTag.y);//是否在误差范围内boolean locked = selectTag == null || (absX < 2 && absY < 2);if (locked) {//锁定完成,恢复平静---mAngleX = mAngleY = 0;touch_state = State.Calm;handler.postDelayed(this, 16);View child = getChildAt(selectIndex);child.setSelected(true);if (onItemSelectListener != null) {onItemSelectListener.onSelectLockedComplete(child, selectIndex, selectTag);}return;}if (absX > absY) {if (absX < minLockError) {mAngleX = mAngleY = 0;} else {mAngleY = Math.max(absX / radius * 3.5f, .5f);mAngleX = absY / absX * mAngleY;mAngleY *= selectTag.x / absX;mAngleX *= -selectTag.y / absY;}} else {if (absY < minLockError) {mAngleY = mAngleX = 0;} else {mAngleX = Math.max(absY / radius * 3.5f, .5f);mAngleY = absX / absY * mAngleX;mAngleX *= -selectTag.y / absY;mAngleY *= selectTag.x / absX;}}processTouch();}handler.postDelayed(this, 16);}@Overrideprotected void dispatchDraw(Canvas canvas) {//绘制影子canvas.drawBitmap(shadowBitmap, null, shadowRect, glancePaint);//绘制地球仪canvas.drawCircle(centerX, centerY, radius, paint);//绘制光照canvas.drawBitmap(glanceBitmap, null, glanceRect, glancePaint);super.dispatchDraw(canvas);//画线/* List<Tag> list = mTagCloud.getTagList();if (list != null && !list.isEmpty()) {Path path = new Path();Tag lastTag = mTagCloud.get(0);path.moveTo(centerX + lastTag.getX(), centerY + lastTag.getY());for (int i = 1; i < getChildCount(); i++) {Tag tag = mTagCloud.get(i);path.lineTo(centerX + tag.x, centerY + tag.y);}paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(4);canvas.drawPath(path, paint);}*/}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);centerX = w / 2;centerY = h / 2;radius = radiusPercent * Math.min(centerX, centerY);glanceRect.set(centerX - radius, centerY - radius, centerX + radius, centerY + radius);shadowRect.set(centerX, centerY + radius * 0.8f, w, centerY + radius * 1.05f);}//开始自动锁定itemprivate void startAutoLockItem() {if (selectTag != null) {getChildAt(selectIndex).setSelected(false);selectTag = null;}//选出距离最近的itemfloat max = Integer.MAX_VALUE;for (int i = 0; i < getChildCount(); i++) {Tag tag = mTagCloud.get(i);if (tag.getScale() > 0.9) {double distance = tag.distance();if (distance < max) {max = (float) distance;selectTag = tag;selectIndex = i;}}}if (selectTag != null) {touch_state = State.AutoLock;if (onItemSelectListener != null) {onItemSelectListener.onSelect(getChildAt(selectIndex), selectIndex, selectTag);}}}public void setOnItemSelectListener(OnItemSelectListener onItemSelectListener) {this.onItemSelectListener = onItemSelectListener;}private OnItemSelectListener onItemSelectListener;public interface OnItemSelectListener {void onSelect(View child, int position, Tag tag);void onSelectLockedComplete(View child, int position, Tag tag);}
}
自定义属性
<declare-styleable name="TagCloudView"><attr name="lightColor" format="color" /><attr name="darkColor" format="color" /><attr name="radiusPercent" format="float" /><attr name="scrollSpeed" format="float" /><attr name="sphereColor" format="color" /><attr name="autoLockSelect" format="boolean" /></declare-styleable>
缺少一些Resource的话,参照:球体
阴影:
R.dimen._1dp ,这个就是<dimen name="_1dp">1dp</dimen>
调用示例
package com.example.user.third;import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;import com.example.user.mytest.R;/*** Created by user on 2016/12/7.*/
public class ThirdActivity extends Activity {TagCloudView cloudView;private int[] ss = {R.mipmap.baby1, R.mipmap.jt, R.mipmap.liy, R.mipmap.lyf1, R.mipmap.ym, R.mipmap.mm1, R.mipmap.mm2, R.mipmap.baby1, R.mipmap.jt, R.mipmap.liy, R.mipmap.lyf1, R.mipmap.ym, R.mipmap.mm1, R.mipmap.mm2};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_third);cloudView = (TagCloudView) findViewById(R.id.tagCloudView1);cloudView.setAdapter(new TagsAdapter() {@Overridepublic int getCount() {return ss.length;}@Overridepublic View getView(Context context, int position, ViewGroup parent) {ImageView imageView = new ImageView(context);imageView.setLayoutParams(new ViewGroup.LayoutParams(160, 160));imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);imageView.setImageResource(ss[position]);return imageView;}@Overridepublic Object getItem(int position) {return ss[position];}@Overridepublic int getPopularity(int position) {return ss.length - position;}});cloudView.setOnItemSelectListener(new TagCloudView.OnItemSelectListener() {@Overridepublic void onSelect(View child, int position, Tag tag) {//准备锁定child时调用}@Overridepublic void onSelectLockedComplete(View child, int position, Tag tag) {//当锁定item的动画完成后调用}});}
}
<com.dxtx.widget.tagcloud.TagCloudViewandroid:id="@+id/tagCloudView1"android:layout_width="match_parent"android:layout_height="match_parent"app:sphereColor="#0ff"app:radiusPercent="0.9"app:scrollSpeed="3"app:autoLockSelect="true"tools:visibility="visible" />
自定义控件往往与项目要的效果有些出入,还是要理解了,然后再修改些内容再使用最好
完整代码稍后上传
这篇关于地球仪式分布的控件,球体控件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!