├── ActionBarToast.java ├── AmountEditText.java ├── BaseRecyclerAdapter.java ├── CustomTouchView.java ├── LoadingView.java ├── LogUtil.java ├── MaskView.java ├── PhoneEditText.java ├── README.md ├── ScratchCard.java ├── ScreenSizeUtils.java ├── SharePreUtils.java ├── TimeClock.java ├── TimeUtils.java ├── WaveView.java ├── WordsNavigation.java └── effectImage ├── 20161105231805323.gif ├── 20161118122207199.png └── custom_touch_view.gif /ActionBarToast.java: -------------------------------------------------------------------------------- 1 | package com.azhon.lightmode.view; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ObjectAnimator; 6 | import android.content.Context; 7 | import android.content.res.TypedArray; 8 | import android.support.annotation.IntDef; 9 | import android.support.constraint.ConstraintLayout; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | 15 | import com.azhon.lightmode.R; 16 | 17 | import java.lang.annotation.Retention; 18 | import java.lang.annotation.RetentionPolicy; 19 | 20 | /** 21 | * 文件名: ActionBarToast 22 | * 创建时间: 2018/1/4 on 21:09 23 | * 描述: TODO 仿QQ提示信息弹框 24 | *

25 | * 使用方法(在Activity中): 26 | *

27 | * ActionBarToast barToast = new ActionBarToast(this); 28 | * barToast.showToast(ActionBarToast.LENGTH_SHORT); 29 | *

30 | * 31 | * @author 阿钟 32 | */ 33 | 34 | public class ActionBarToast { 35 | 36 | /** 37 | * activity 根视图 38 | */ 39 | private ViewGroup decorView; 40 | /** 41 | * activity 根视图 42 | */ 43 | private View view; 44 | /** 45 | * 状态栏加上actionBar的高度 46 | */ 47 | private int totalHeight; 48 | /** 49 | * 是否正在显示 50 | */ 51 | private boolean showing; 52 | /** 53 | * 显示/退出 动画的执行时间 54 | */ 55 | private int animationDuration = 150; 56 | /** 57 | * 短时间显示 ms 58 | */ 59 | public static final int LENGTH_SHORT = 1500; 60 | /** 61 | * 长时间显示 ms 62 | */ 63 | public static final int LENGTH_LONG = 2000; 64 | /** 65 | * 不消失,需要手动调用{@link #cancel()} 66 | */ 67 | public static final int LENGTH_SHOW = -1; 68 | 69 | /** 70 | * @ IntDef 代替枚举 71 | */ 72 | @IntDef({LENGTH_SHORT, LENGTH_LONG, LENGTH_SHOW}) 73 | @Retention(RetentionPolicy.SOURCE) 74 | public @interface Duration { 75 | } 76 | 77 | public ActionBarToast(AppCompatActivity activity) { 78 | decorView = (ViewGroup) activity.getWindow().getDecorView(); 79 | view = LayoutInflater.from(activity).inflate(R.layout.item_toast, null); 80 | //设置View的高度 81 | totalHeight = getHeight(activity, view.findViewById(R.id.status_bar)); 82 | view.setLayoutParams(new ViewGroup.LayoutParams( 83 | ViewGroup.LayoutParams.MATCH_PARENT, totalHeight)); 84 | } 85 | 86 | /** 87 | * 显示 88 | * 89 | * @param duration 显示时长 90 | */ 91 | public void showToast(@Duration int duration) { 92 | if (!showing) { 93 | showing = true; 94 | decorView.addView(view); 95 | ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationY", -totalHeight, 0f); 96 | animator.setDuration(animationDuration); 97 | animator.start(); 98 | if (duration != LENGTH_SHOW) { 99 | //一段时间后隐藏 100 | view.postDelayed(runnable, duration); 101 | } 102 | } 103 | } 104 | 105 | /** 106 | * 延时执行取消操作 107 | */ 108 | private Runnable runnable = new Runnable() { 109 | @Override 110 | public void run() { 111 | cancel(); 112 | } 113 | }; 114 | 115 | /** 116 | * 取消展示 117 | */ 118 | public void cancel() { 119 | if (!showing) { 120 | return; 121 | } 122 | //手动移除异步计时器 123 | view.removeCallbacks(runnable); 124 | 125 | ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationY", 0f, -totalHeight); 126 | animator.setDuration(animationDuration); 127 | animator.addListener(new AnimatorListenerAdapter() { 128 | @Override 129 | public void onAnimationEnd(Animator animation) { 130 | if (view.getParent() != null) { 131 | ((ViewGroup) view.getParent()).removeView(view); 132 | showing = false; 133 | } 134 | } 135 | }); 136 | animator.start(); 137 | } 138 | 139 | /** 140 | * 是否正在展示 141 | * 142 | * @return {@link #showing} 143 | */ 144 | public boolean isShowing() { 145 | return showing; 146 | } 147 | 148 | /** 149 | * 获取状态栏 + 标题栏 的高度 150 | */ 151 | private int getHeight(Context context, View v) { 152 | //标题栏 153 | TypedArray values = context.getTheme().obtainStyledAttributes(new int[]{android.R.attr.actionBarSize}); 154 | int actionBarHeight = values.getDimensionPixelSize(0, 0); 155 | values.recycle(); 156 | //状态栏 157 | int statusBarHeight = 0; 158 | int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); 159 | if (resourceId > 0) { 160 | //设置布局 占位视图的高度 161 | statusBarHeight = context.getResources().getDimensionPixelSize(resourceId); 162 | 163 | v.setLayoutParams(new ConstraintLayout.LayoutParams( 164 | ConstraintLayout.LayoutParams.MATCH_PARENT, statusBarHeight)); 165 | } 166 | return actionBarHeight + statusBarHeight; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /AmountEditText.java: -------------------------------------------------------------------------------- 1 | package cn.hjtech.pigeon.common.view; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.AppCompatEditText; 5 | import android.text.Editable; 6 | import android.text.InputFilter; 7 | import android.text.Spanned; 8 | import android.text.TextUtils; 9 | import android.text.TextWatcher; 10 | import android.text.method.DigitsKeyListener; 11 | import android.util.AttributeSet; 12 | 13 | /** 14 | * 2017年5月29日23:59:32 15 | * 16 | * @author 阿钟 17 | * @version 1.0 18 | * TODO 金额输入框,只能输入两位小数 19 | * 20 | * @version 1.1 21 | * TODO 添加文本改变监听 22 | */ 23 | public class AmountEditText extends AppCompatEditText implements TextWatcher { 24 | 25 | 26 | public AmountEditText(Context context, AttributeSet attrs) { 27 | super(context, attrs); 28 | init(); 29 | } 30 | 31 | public AmountEditText(Context context) { 32 | super(context); 33 | init(); 34 | } 35 | 36 | private void init() { 37 | //设置输入框允许输入的类型(正则) 38 | setKeyListener(DigitsKeyListener.getInstance("0123456789.")); 39 | //设置输入字符 40 | setFilters(new InputFilter[]{inputFilter}); 41 | addTextChangedListener(this); 42 | } 43 | 44 | private InputFilter inputFilter = new InputFilter() { 45 | @Override 46 | public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { 47 | // 删除等特殊字符,直接返回 48 | if (TextUtils.isEmpty(source)) { 49 | return null; 50 | } 51 | String dValue = dest.toString(); 52 | String[] splitArray = dValue.split("\\."); 53 | if (splitArray.length > 1) { 54 | String dotValue = splitArray[1]; 55 | int diff = dotValue.length() + 1 - 2;//2表示输入框的小数位数 56 | if (diff > 0) { 57 | return source.subSequence(start, end - diff); 58 | } 59 | } 60 | return null; 61 | } 62 | }; 63 | 64 | @Override 65 | public void onTextChanged(CharSequence s, int start, int before, int count) { 66 | if (TextUtils.isEmpty(s)) { 67 | return; 68 | } 69 | //第一个字符不为小数点 70 | if (s.length() == 1 && s.toString().equals(".")) { 71 | setText(""); 72 | return; 73 | } 74 | int counter = counter(s.toString(), '.'); 75 | if (counter > 1) { 76 | //小数点第一次出现的位置 77 | int index = s.toString().indexOf('.'); 78 | setText(s.subSequence(0, index + 1)); 79 | } 80 | setSelection(getText().toString().length()); 81 | } 82 | 83 | /** 84 | * 统计一个字符在字符串中出现的次数 85 | * 86 | * @param s 字符串 87 | * @param c 字符 88 | * @return 數量 89 | */ 90 | public int counter(String s, char c) { 91 | int count = 0; 92 | for (int i = 0; i < s.length(); i++) { 93 | if (s.charAt(i) == c) { 94 | count++; 95 | } 96 | } 97 | return count; 98 | } 99 | 100 | @Override 101 | public void afterTextChanged(Editable s) { 102 | if (listener != null) { 103 | listener.onTextChanged(getText().toString()); 104 | } 105 | } 106 | 107 | @Override 108 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 109 | } 110 | 111 | public OnTextChangeListener listener; 112 | 113 | public void setListener(OnTextChangeListener listener) { 114 | this.listener = listener; 115 | } 116 | 117 | public interface OnTextChangeListener { 118 | 119 | void onTextChanged(String s); 120 | 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /BaseRecyclerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.azhong.adapter; 2 | 3 | import android.support.annotation.DrawableRes; 4 | import android.support.annotation.LayoutRes; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.support.v7.widget.RecyclerView.ViewHolder; 7 | import android.util.SparseArray; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.ImageView; 12 | import android.widget.TextView; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | /* 18 | * 文件名: BaseRecyclerAdapter 19 | * 创建者: ZSY 20 | * 创建时间: 2017/3/20 on 9:42 21 | * 描述: TODO RecyclerView的封装适配器 22 | */ 23 | public abstract class BaseRecyclerAdapter extends RecyclerView.Adapter 24 | implements View.OnClickListener, View.OnLongClickListener { 25 | /** 26 | * 数据源 27 | */ 28 | private List data; 29 | /** 30 | * 布局资源id 31 | */ 32 | private int layoutResId; 33 | /** 34 | * 头部布局id 35 | */ 36 | private int headView = -1; 37 | /** 38 | * 底部部布局id 39 | */ 40 | private int footView = -1; 41 | /** 42 | * 数据显示itemType 43 | */ 44 | private final int ITEM_TYPE = 870371; 45 | /** 46 | * 头部itemType 47 | */ 48 | private final int HEAD_TYPE = 870372; 49 | /** 50 | * 底部itemType 51 | */ 52 | private final int FOOT_TYPE = 870373; 53 | /** 54 | * Item点击事件 55 | */ 56 | private onItemClickListener clickListener; 57 | /** 58 | * Item长按事件 59 | */ 60 | private onItemLongClickListener longListener; 61 | 62 | public BaseRecyclerAdapter(@LayoutRes int layoutResId, List data) { 63 | this.data = data == null ? new ArrayList() : data; 64 | if (layoutResId != 0) { 65 | this.layoutResId = layoutResId; 66 | } else { 67 | throw new NullPointerException("请设置Item资源id"); 68 | } 69 | } 70 | 71 | @Override 72 | public VH onCreateViewHolder(ViewGroup parent, int viewType) { 73 | View view = null; 74 | switch (viewType) { 75 | case ITEM_TYPE: 76 | view = LayoutInflater.from(parent.getContext()).inflate(layoutResId, parent, false); 77 | break; 78 | case HEAD_TYPE: 79 | view = LayoutInflater.from(parent.getContext()).inflate(headView, parent, false); 80 | break; 81 | case FOOT_TYPE: 82 | view = LayoutInflater.from(parent.getContext()).inflate(footView, parent, false); 83 | break; 84 | } 85 | return (VH) new BaseViewHolder(view); 86 | } 87 | 88 | @Override 89 | public int getItemViewType(int position) { 90 | int type = ITEM_TYPE; 91 | //如果添加了头部view 92 | if (position == 0 && headView != -1) { 93 | type = HEAD_TYPE; 94 | } 95 | //如果添加了底部view 96 | if (position == getItemCount() - 1 && footView != -1) { 97 | type = FOOT_TYPE; 98 | } 99 | return type; 100 | } 101 | 102 | @Override 103 | public void onBindViewHolder(VH holder, int position) { 104 | switch (holder.getItemViewType()) { 105 | case ITEM_TYPE: 106 | //设置Item的点击事件 107 | holder.itemView.setOnClickListener(this); 108 | //设置Item的长按事件 109 | holder.itemView.setOnLongClickListener(this); 110 | if (headView == -1) { 111 | holder.itemView.setTag(position); 112 | bindTheData(holder, data.get(position), position); 113 | } else { 114 | holder.itemView.setTag(position - 1); 115 | bindTheData(holder, data.get(position - 1), position - 1); 116 | } 117 | break; 118 | case HEAD_TYPE: 119 | break; 120 | case FOOT_TYPE: 121 | break; 122 | } 123 | } 124 | 125 | @Override 126 | public int getItemCount() { 127 | if (headView != -1 && footView != -1) { 128 | return data.size() + 2; 129 | } 130 | if (headView != -1) { 131 | return data.size() + 1; 132 | } 133 | if (footView != -1) { 134 | return data.size() + 1; 135 | } 136 | return data.size(); 137 | } 138 | 139 | /** 140 | * 清除数据 141 | */ 142 | public void clearData() { 143 | if (data != null && data.size() > 0) { 144 | data.clear(); 145 | } 146 | notifyDataSetChanged(); 147 | } 148 | 149 | /** 150 | * 绑定数据 151 | * 152 | * @param holder 视图管理者 153 | * @param data 数据源 154 | */ 155 | protected abstract void bindTheData(VH holder, D data, int position); 156 | 157 | /** 158 | * 添加头部View 159 | */ 160 | public void addHeadView(@LayoutRes int id) { 161 | this.headView = id; 162 | } 163 | 164 | /** 165 | * 添加底部View 166 | */ 167 | public void addFootView(@LayoutRes int id) { 168 | this.footView = id; 169 | } 170 | 171 | @Override 172 | public void onClick(View v) { 173 | //点击回调 174 | if (clickListener != null) { 175 | clickListener.onItemClick((Integer) v.getTag(), v); 176 | } 177 | } 178 | 179 | @Override 180 | public boolean onLongClick(View v) { 181 | //长按回调 182 | return longListener != null && longListener.onItemLonClick((Integer) v.getTag(), v); 183 | } 184 | 185 | /********************************************* 186 | * 基础视图管理者 187 | *********************************************/ 188 | public class BaseViewHolder extends ViewHolder { 189 | /** 190 | * 集合类,layout里包含的View,以view的id作为key,value是view对象 191 | */ 192 | private SparseArray mViews; 193 | 194 | public BaseViewHolder(View itemView) { 195 | super(itemView); 196 | mViews = new SparseArray<>(); 197 | } 198 | 199 | public T findViewById(int viewId) { 200 | View view = mViews.get(viewId); 201 | if (view == null) { 202 | view = itemView.findViewById(viewId); 203 | mViews.put(viewId, view); 204 | } 205 | return (T) view; 206 | } 207 | 208 | /** 209 | * 设置文本资源 210 | * 211 | * @param viewId view id 212 | * @param s 字符 213 | */ 214 | public TextView setText(int viewId, CharSequence s) { 215 | TextView view = findViewById(viewId); 216 | view.setText(s); 217 | return view; 218 | } 219 | 220 | /** 221 | * 设置图片资源 222 | * 223 | * @param viewId view id 224 | * @param imageResId 图片资源id 225 | */ 226 | public ImageView setImageResource(int viewId, @DrawableRes int imageResId) { 227 | ImageView view = findViewById(viewId); 228 | view.setImageResource(imageResId); 229 | return view; 230 | } 231 | 232 | 233 | } 234 | 235 | /** 236 | * 设置点击监听 237 | * 238 | * @param clickListener 监听器 239 | */ 240 | public void setItemClickListener(onItemClickListener clickListener) { 241 | this.clickListener = clickListener; 242 | } 243 | 244 | /** 245 | * 设置长按监听 246 | * 247 | * @param longListener 监听器 248 | */ 249 | public void setItemLongClickListener(onItemLongClickListener longListener) { 250 | this.longListener = longListener; 251 | } 252 | 253 | public interface onItemClickListener { 254 | void onItemClick(int position, View v); 255 | } 256 | 257 | public interface onItemLongClickListener { 258 | boolean onItemLonClick(int position, View v); 259 | } 260 | } -------------------------------------------------------------------------------- /CustomTouchView.java: -------------------------------------------------------------------------------- 1 | package com.azhon.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Rect; 8 | import android.util.AttributeSet; 9 | import android.view.MotionEvent; 10 | import android.view.VelocityTracker; 11 | import android.view.View; 12 | import android.view.ViewConfiguration; 13 | import android.widget.OverScroller; 14 | 15 | import androidx.annotation.Nullable; 16 | 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | import java.util.Random; 20 | 21 | /** 22 | * 文件名: CustomTouchView 23 | * 创建时间: 2019-04-26 on 15:51 24 | * 描述: TODO 自定义View,触摸交互 25 | * 26 | * @author 阿钟 27 | */ 28 | 29 | public class CustomTouchView extends View { 30 | /** 31 | * 上下文 32 | */ 33 | private Context context; 34 | /** 35 | * 每一个矩形的宽度 36 | */ 37 | private int rectWidth = 120; 38 | /** 39 | * 绘制多少个矩形 40 | */ 41 | private int rectCount = 23; 42 | /** 43 | * 宽度 44 | */ 45 | private int width; 46 | /** 47 | * 高度 48 | */ 49 | private int height; 50 | /** 51 | * 内容区域的宽度 52 | */ 53 | private int contentWidth; 54 | /** 55 | * 矩形画笔 56 | */ 57 | private Paint paint; 58 | /** 59 | * 文字画笔 60 | */ 61 | private Paint textPaint; 62 | /** 63 | * 速度获取 64 | */ 65 | private VelocityTracker velocityTracker; 66 | /** 67 | * 惯性最大 最小速度 68 | */ 69 | private int maximumVelocity, minimumVelocity; 70 | /** 71 | * 控制滑动 72 | */ 73 | private OverScroller scroller; 74 | /** 75 | * 非法触控id 76 | */ 77 | private final static int INVALID_ID = -1; 78 | /** 79 | * 记录首个触控点的id 避免多点触控引起的滚动 80 | */ 81 | private int activePointerId = INVALID_ID; 82 | /** 83 | * 最小可滑动值、最大可滑动值 84 | */ 85 | private int minScrollX = 0, maxScrollX = 0; 86 | /** 87 | * 手指上次滑动的点 88 | */ 89 | private float lastX; 90 | 91 | /** 92 | * 保存随机生成的颜色 93 | */ 94 | private Map colors = new HashMap<>(); 95 | 96 | 97 | public CustomTouchView(Context context) { 98 | this(context, null); 99 | } 100 | 101 | public CustomTouchView(Context context, @Nullable AttributeSet attrs) { 102 | super(context, attrs); 103 | this.context = context; 104 | 105 | paint = new Paint(); 106 | paint.setColor(Color.MAGENTA); 107 | paint.setAntiAlias(true); 108 | 109 | textPaint = new Paint(); 110 | textPaint.setColor(Color.WHITE); 111 | textPaint.setTextSize(32); 112 | textPaint.setStyle(Paint.Style.FILL); 113 | //该方法即为设置基线上那个点究竟是left,center,还是right 这里我设置为center 114 | textPaint.setTextAlign(Paint.Align.CENTER); 115 | 116 | //最小的惯性滚动速度 117 | maximumVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity(); 118 | minimumVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity(); 119 | 120 | scroller = new OverScroller(context); 121 | 122 | } 123 | 124 | @Override 125 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 126 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 127 | int defaultWidth = DensityUtil.dip2px(context, 300); 128 | int defaultHeight = DensityUtil.dip2px(context, 200); 129 | width = getViewSize(defaultWidth, widthMeasureSpec); 130 | height = getViewSize(defaultHeight, heightMeasureSpec); 131 | //设置View的大小 132 | setMeasuredDimension(width, height); 133 | 134 | //最大的滚动距离为内容的宽度 - View的宽度 - 最右边多算的一个空白区域 135 | contentWidth = rectCount * rectWidth * 2 - rectWidth; 136 | maxScrollX = contentWidth - width; 137 | } 138 | 139 | @Override 140 | protected void onDraw(Canvas canvas) { 141 | super.onDraw(canvas); 142 | for (int i = 0; i < rectCount; i++) { 143 | int right = i * 2 * rectWidth + rectWidth; 144 | Rect rect = new Rect(i * 2 * rectWidth, height / 4, right, 3 * height / 4); 145 | paint.setColor(randomColor(i)); 146 | canvas.drawRect(rect, paint); 147 | drawText(canvas, rect, String.valueOf(i + 1), textPaint); 148 | } 149 | } 150 | 151 | /** 152 | * 将文字绘制在矩形中间 153 | */ 154 | private void drawText(Canvas canvas, Rect rect, String text, Paint paint) { 155 | Paint.FontMetrics fontMetrics = paint.getFontMetrics(); 156 | //为基线到字体上边框的距离,即上图中的top 157 | float top = fontMetrics.top; 158 | //为基线到字体下边框的距离,即上图中的bottom 159 | float bottom = fontMetrics.bottom; 160 | int baseLineY = (int) (rect.centerY() - top / 2 - bottom / 2); 161 | //基线中间点的y轴计算公式 162 | canvas.drawText(text, rect.centerX(), baseLineY, paint); 163 | } 164 | 165 | /** 166 | * 获取View的宽高 167 | */ 168 | private int getViewSize(int defaultSize, int measureSpec) { 169 | int mode = MeasureSpec.getMode(measureSpec); 170 | int size = MeasureSpec.getSize(measureSpec); 171 | switch (mode) { 172 | //wrap_content 173 | case MeasureSpec.AT_MOST: 174 | case MeasureSpec.UNSPECIFIED: 175 | return defaultSize; 176 | //match_parent、固定dp大小 177 | case MeasureSpec.EXACTLY: 178 | return size; 179 | } 180 | return defaultSize; 181 | } 182 | 183 | 184 | @Override 185 | public boolean onTouchEvent(MotionEvent event) { 186 | //内容区域的宽度是否大于View的宽度,如果没有大于View的宽度则不需要滚动 187 | if (contentWidth <= width) return super.onTouchEvent(event); 188 | //开始速度检测 189 | if (velocityTracker == null) { 190 | velocityTracker = VelocityTracker.obtain(); 191 | } 192 | velocityTracker.addMovement(event); 193 | switch (event.getAction()) { 194 | case MotionEvent.ACTION_DOWN: 195 | //记录首个触控点的id 196 | activePointerId = event.findPointerIndex(event.getActionIndex()); 197 | if (!scroller.isFinished()) { 198 | scroller.abortAnimation(); 199 | } 200 | lastX = event.getX(); 201 | break; 202 | case MotionEvent.ACTION_MOVE: 203 | if (activePointerId == INVALID_ID || event.findPointerIndex(activePointerId) == INVALID_ID) { 204 | break; 205 | } 206 | //计算首个触控点移动后的坐标 207 | float moveX = lastX - event.getX(activePointerId); 208 | lastX = event.getX(activePointerId); 209 | damping((int) moveX); 210 | break; 211 | case MotionEvent.ACTION_UP: 212 | activePointerId = INVALID_ID; 213 | //处理松手后的Fling 214 | velocityTracker.computeCurrentVelocity(1000, maximumVelocity); 215 | int velocityX = (int) velocityTracker.getXVelocity(); 216 | //滑动速度大于默认速度,则开始惯性滑动 217 | if (Math.abs(velocityX) > minimumVelocity) { 218 | startFling(velocityX); 219 | } 220 | restoreLeft(); 221 | restoreRight(); 222 | recycleVelocity(); 223 | break; 224 | case MotionEvent.ACTION_CANCEL: 225 | activePointerId = INVALID_ID; 226 | if (!scroller.isFinished()) { 227 | scroller.abortAnimation(); 228 | } 229 | recycleVelocity(); 230 | break; 231 | } 232 | return true; 233 | } 234 | 235 | 236 | /** 237 | * 当持续向右滑动的时候,放手回到起点 238 | */ 239 | private void restoreLeft() { 240 | //不能继续往右边滑动了 241 | if (getScrollX() < 0) { 242 | startFling(-50); 243 | invalidate(); 244 | } 245 | } 246 | 247 | /** 248 | * 当持续向左滑动的时候,放手定位到最右边 249 | */ 250 | private void restoreRight() { 251 | //不能继续往右边滑动了 252 | if (getScrollX() > maxScrollX) { 253 | startFling(50); 254 | invalidate(); 255 | } 256 | } 257 | 258 | /** 259 | * 阻尼效果 260 | */ 261 | private void damping(int moveX) { 262 | //持续在向左滑动 和 持续向右滑动 263 | if (getScrollX() > maxScrollX || getScrollX() < minScrollX) { 264 | scrollBy(moveX / 3, 0); 265 | } else { 266 | scrollBy(moveX, 0); 267 | } 268 | } 269 | 270 | /** 271 | * VelocityTracker回收 272 | */ 273 | private void recycleVelocity() { 274 | if (velocityTracker != null) { 275 | velocityTracker.recycle(); 276 | velocityTracker = null; 277 | } 278 | } 279 | 280 | 281 | /** 282 | * 开始的、惯性滚动 283 | * 284 | * @param velocityX x轴速度 285 | */ 286 | private void startFling(int velocityX) { 287 | scroller.fling(getScrollX(), 0, -velocityX / 2, 0, minScrollX, maxScrollX, 0, 0); 288 | invalidate(); 289 | } 290 | 291 | 292 | /** 293 | * 由fling方法发起的invalidate,会最终调用view的computeScroll方法 294 | * 而在computeScroll方法中又发起了postInvalidate,最终又会调用view的computeScroll,如此循环绘制形成了惯性滚动 295 | */ 296 | @Override 297 | public void computeScroll() { 298 | if (scroller.computeScrollOffset()) { 299 | scrollTo(scroller.getCurrX(), scroller.getCurrY()); 300 | postInvalidate(); 301 | } 302 | } 303 | 304 | /** 305 | * 随机生成颜色 306 | */ 307 | private int randomColor(int position) { 308 | Integer color = colors.get(position); 309 | if (color != null) { 310 | return color; 311 | } 312 | Random random = new Random(); 313 | int r = random.nextInt(256); 314 | int g = random.nextInt(256); 315 | int b = random.nextInt(256); 316 | color = Color.rgb(r, g, b); 317 | colors.put(position, color); 318 | return color; 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /LoadingView.java: -------------------------------------------------------------------------------- 1 | package com.zsy.roate; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.graphics.Canvas; 7 | import android.graphics.Color; 8 | import android.graphics.Paint; 9 | import android.util.AttributeSet; 10 | import android.view.View; 11 | import android.view.animation.BounceInterpolator; 12 | 13 | /* 14 | *以下为自定义的属性,如需使用需在attrs.xml文件中添加以下代码 15 | * 16 | * 17 | * 18 | * 19 | * 20 | * 21 | * 22 | * 23 | * 24 | * 25 | * 26 | * 27 | * 28 | * 29 | * 30 | * 31 | * 32 | * 33 | */ 34 | /* 35 | * 文件名: LoadingView 36 | * 创建者: 阿钟 37 | * 创建时间: 2016/11/9 22:05 38 | * 描述: 自定义菊花进度条 39 | */ 40 | public class LoadingView extends View { 41 | 42 | /*view的默认宽度*/ 43 | private int mWidth; 44 | /*view的默认高度*/ 45 | private int mHeight; 46 | /*线条粗细*/ 47 | private int paintBold; 48 | /*线条长度*/ 49 | private int lineLength; 50 | /*背景线条颜色*/ 51 | private int bgPaintColor; 52 | /*上层线条颜色*/ 53 | private int beforePaintColor; 54 | /*进度文字颜色*/ 55 | private int textColor; 56 | /*线条个数*/ 57 | private int lines; 58 | /*背景画笔*/ 59 | private Paint bgPaint; 60 | /*前景画笔*/ 61 | private Paint bfPaint; 62 | /*进度文字画笔*/ 63 | private Paint textPaint; 64 | /*当前下载进度*/ 65 | private int progress; 66 | /*最大进度*/ 67 | private int max; 68 | 69 | public LoadingView(Context context) { 70 | super(context); 71 | } 72 | 73 | public LoadingView(Context context, AttributeSet attrs) { 74 | super(context, attrs); 75 | loadAttrs(context, attrs); 76 | initPaint(); 77 | } 78 | 79 | public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) { 80 | super(context, attrs, defStyleAttr); 81 | } 82 | 83 | /** 84 | * 加载我们在attrs.xml文件的自定义的属性 85 | */ 86 | private void loadAttrs(Context context, AttributeSet attrs) { 87 | TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.a_zhon); 88 | paintBold = array.getDimensionPixelSize(R.styleable.a_zhon_paintBold, 10); 89 | lineLength = array.getDimensionPixelSize(R.styleable.a_zhon_lineLength, 25); 90 | bgPaintColor = array.getColor(R.styleable.a_zhon_backgroundColor, Color.GRAY); 91 | beforePaintColor = array.getColor(R.styleable.a_zhon_beforeColor, Color.YELLOW); 92 | lines = array.getInt(R.styleable.a_zhon_lines, 20); 93 | max = array.getInt(R.styleable.a_zhon_max, 100); 94 | progress = array.getInt(R.styleable.a_zhon_progress, 0); 95 | textColor = array.getColor(R.styleable.a_zhon_textColor, Color.BLACK); 96 | array.recycle(); 97 | } 98 | 99 | /** 100 | * 初始化画笔 101 | */ 102 | private void initPaint() { 103 | bgPaint = new Paint(); 104 | bgPaint.setColor(bgPaintColor); 105 | bgPaint.setAntiAlias(true); 106 | bgPaint.setStrokeWidth(paintBold); 107 | //使得画笔更加圆滑 108 | bgPaint.setStrokeJoin(Paint.Join.ROUND); 109 | bgPaint.setStrokeCap(Paint.Cap.ROUND); 110 | 111 | bfPaint = new Paint(); 112 | bfPaint.setColor(beforePaintColor); 113 | bfPaint.setAntiAlias(true); 114 | bfPaint.setStrokeWidth(paintBold); 115 | bfPaint.setStrokeJoin(Paint.Join.ROUND); 116 | bfPaint.setStrokeCap(Paint.Cap.ROUND); 117 | 118 | textPaint = new Paint(); 119 | textPaint.setColor(textColor); 120 | textPaint.setAntiAlias(true); 121 | textPaint.setTextAlign(Paint.Align.CENTER); 122 | textPaint.setTextSize(40); 123 | } 124 | 125 | @Override 126 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 127 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 128 | //获取view的宽度 129 | mWidth = getViewSize(100, widthMeasureSpec); 130 | //获取view的高度 131 | mHeight = getViewSize(100, heightMeasureSpec); 132 | } 133 | 134 | /** 135 | * 测量模式 表示意思 136 | * UNSPECIFIED 父容器没有对当前View有任何限制,当前View可以任意取尺寸 137 | * EXACTLY 当前的尺寸就是当前View应该取的尺寸 138 | * AT_MOST 当前尺寸是当前View能取的最大尺寸 139 | * 140 | * @param defaultSize 默认大小 141 | * @param measureSpec 包含测量模式和宽高信息 142 | * @return 返回View的宽高大小 143 | */ 144 | private int getViewSize(int defaultSize, int measureSpec) { 145 | int viewSize = defaultSize; 146 | //获取测量模式 147 | int mode = MeasureSpec.getMode(measureSpec); 148 | //获取大小 149 | int size = MeasureSpec.getSize(measureSpec); 150 | switch (mode) { 151 | case MeasureSpec.UNSPECIFIED: //如果没有指定大小,就设置为默认大小 152 | viewSize = defaultSize; 153 | break; 154 | case MeasureSpec.AT_MOST: //如果测量模式是最大取值为size 155 | //我们将大小取最大值,你也可以取其他值 156 | viewSize = size; 157 | break; 158 | case MeasureSpec.EXACTLY: //如果是固定的大小,那就不要去改变它 159 | viewSize = size; 160 | break; 161 | } 162 | return viewSize; 163 | } 164 | 165 | @Override 166 | protected void onDraw(Canvas canvas) { 167 | super.onDraw(canvas); 168 | int x = mWidth / 2; 169 | int y = mHeight / 2; 170 | int r = x - 5; 171 | for (int i = 0; i < lines; i++) { 172 | //绘制下层菊花 173 | canvas.drawLine(x, y - r, x, y - r + lineLength, bgPaint); 174 | canvas.rotate(360 / lines, x, y); 175 | } 176 | //获取需要绘制多少个刻度 177 | int count = (progress * lines) / max; 178 | //绘制中间的文字进度 179 | canvas.drawText((progress * 100 / max) + "%", x, y + 5, textPaint); 180 | //绘制上层菊花,也就是进度 181 | canvas.rotate(360 / lines, x, y); 182 | for (; count > 0; count--) { 183 | canvas.drawLine(x, y - r, x, y - r + lineLength, bfPaint); 184 | canvas.rotate(360 / lines, x, y); 185 | } 186 | } 187 | 188 | /** 189 | * 为进度设置动画 190 | * ValueAnimator是整个属性动画机制当中最核心的一个类,属性动画的运行机制是通过不断地对值进行操作来实现的, 191 | * 而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。 192 | * 它的内部使用一种时间循环的机制来计算值与值之间的动画过渡, 193 | * 我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长, 194 | * 那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。 195 | * 196 | * @param start 开始值 197 | * @param current 结束值 198 | * @param duration 动画时长 199 | */ 200 | public void startAnimation(int start, int current, int duration) { 201 | ValueAnimator progressAnimator = ValueAnimator.ofInt(start, current); 202 | progressAnimator.setDuration(duration); 203 | progressAnimator.setTarget(progress); 204 | progressAnimator.setInterpolator(new BounceInterpolator()); 205 | progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 206 | @Override 207 | public void onAnimationUpdate(ValueAnimator animation) { 208 | progress = (int) animation.getAnimatedValue(); 209 | invalidate(); 210 | } 211 | }); 212 | progressAnimator.start(); 213 | } 214 | 215 | /*设置进度最大值*/ 216 | public void setMax(int max) { 217 | this.max = max; 218 | invalidate(); 219 | } 220 | 221 | /*设置当前进度*/ 222 | public void setProgress(int progress) { 223 | this.progress = progress; 224 | invalidate(); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /LogUtil.java: -------------------------------------------------------------------------------- 1 | package com.zsy.lambda; 2 | 3 | /* 4 | * 文件名: LogUtil 5 | * 创建者: 阿钟 6 | * 创建时间: 2016/10/22 20:21 7 | * 描述: 输出Log的工具类 8 | */ 9 | 10 | import android.util.Log; 11 | 12 | public class LogUtil { 13 | 14 | //Log开关 15 | private static boolean isOpen = true; 16 | 17 | /** 18 | * 设置是否输出Log 19 | * 20 | * @param b 21 | */ 22 | public static void setSwitch(boolean b) { 23 | isOpen = b; 24 | } 25 | 26 | //**********************Debug***********// 27 | public static void d(String tag, String msg) { 28 | if (isOpen) Log.d(tag, msg); 29 | } 30 | 31 | public static void d(String tag, int msg) { 32 | if (isOpen) Log.d(tag, String.valueOf(msg)); 33 | } 34 | 35 | public static void d(String tag, float msg) { 36 | if (isOpen) Log.d(tag, String.valueOf(msg)); 37 | } 38 | 39 | public static void d(String tag, long msg) { 40 | if (isOpen) Log.d(tag, String.valueOf(msg)); 41 | } 42 | 43 | public static void d(String tag, double msg) { 44 | if (isOpen) Log.d(tag, String.valueOf(msg)); 45 | } 46 | 47 | public static void d(String tag, boolean msg) { 48 | if (isOpen) Log.d(tag, String.valueOf(msg)); 49 | } 50 | 51 | //**********************Info***********// 52 | public static void i(String tag, String msg) { 53 | if (isOpen) Log.i(tag, msg); 54 | } 55 | 56 | public static void i(String tag, int msg) { 57 | if (isOpen) Log.i(tag, String.valueOf(msg)); 58 | } 59 | 60 | public static void i(String tag, float msg) { 61 | if (isOpen) Log.i(tag, String.valueOf(msg)); 62 | } 63 | 64 | 65 | public static void i(String tag, long msg) { 66 | if (isOpen) Log.i(tag, String.valueOf(msg)); 67 | } 68 | 69 | public static void i(String tag, double msg) { 70 | if (isOpen) Log.i(tag, String.valueOf(msg)); 71 | } 72 | 73 | public static void i(String tag, boolean msg) { 74 | if (isOpen) Log.i(tag, String.valueOf(msg)); 75 | } 76 | 77 | //**********************Error***********// 78 | public static void e(String tag, String msg) { 79 | if (isOpen) Log.e(tag, msg); 80 | } 81 | 82 | public static void e(String tag, int msg) { 83 | if (isOpen) Log.e(tag, String.valueOf(msg)); 84 | } 85 | 86 | public static void e(String tag, float msg) { 87 | if (isOpen) Log.e(tag, String.valueOf(msg)); 88 | } 89 | 90 | public static void e(String tag, Long msg) { 91 | if (isOpen) Log.e(tag, String.valueOf(msg)); 92 | } 93 | 94 | public static void e(String tag, double msg) { 95 | if (isOpen) Log.e(tag, String.valueOf(msg)); 96 | } 97 | 98 | public static void e(String tag, boolean msg) { 99 | if (isOpen) Log.e(tag, String.valueOf(msg)); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /MaskView.java: -------------------------------------------------------------------------------- 1 | package com.azhon.lightmode.view; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.RectF; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.view.ViewParent; 13 | import android.view.animation.AlphaAnimation; 14 | import android.view.animation.Animation; 15 | 16 | /** 17 | * 项目名: LightMode 18 | * 包名 com.azhon.lightmode 19 | * 文件名: MaskView 20 | * 创建时间: 2018/1/2 on 23:14 21 | * 描述: TODO 模仿网页观看视频,关灯效果;需要在完全显示在屏幕上时,添加才有效 22 | * 23 | * @author 阿钟 24 | */ 25 | 26 | public class MaskView extends View { 27 | 28 | /** 29 | * activity 根视图 30 | */ 31 | private ViewGroup decorView; 32 | /** 33 | * 不加遮罩的视图 34 | */ 35 | private View brightView; 36 | /** 37 | * 遮罩画笔 38 | */ 39 | private Paint paint; 40 | /** 41 | * 不遮罩的view 42 | */ 43 | private RectF brightRectF; 44 | /** 45 | * 遮罩的路径 46 | */ 47 | private Path path; 48 | 49 | /** 50 | * 是否已经添加了 51 | */ 52 | private boolean showing = false; 53 | 54 | public MaskView(Context context) { 55 | super(context); 56 | init(context); 57 | } 58 | 59 | private void init(Context context) { 60 | //获取activity顶层视图 61 | decorView = (ViewGroup) ((Activity) context).getWindow().getDecorView(); 62 | //初始化蒙版视图,充满全屏幕 63 | setLayoutParams(new ViewGroup.LayoutParams( 64 | ViewGroup.LayoutParams.MATCH_PARENT, 65 | ViewGroup.LayoutParams.MATCH_PARENT)); 66 | paint = new Paint(); 67 | //设置为黑色 在加点透明度 68 | paint.setColor(Color.argb(230, 0, 0, 0)); 69 | path = new Path(); 70 | 71 | } 72 | 73 | @Override 74 | protected void onDraw(Canvas canvas) { 75 | super.onDraw(canvas); 76 | if (decorView == null || brightView == null) { 77 | return; 78 | } 79 | //路径恢复 80 | path.reset(); 81 | //A 82 | path.moveTo(brightRectF.left, brightRectF.top); 83 | //B 84 | path.lineTo(brightRectF.right, brightRectF.top); 85 | //C 86 | path.lineTo(brightRectF.right, brightRectF.bottom); 87 | //D 88 | path.lineTo(brightRectF.left, brightRectF.bottom); 89 | //A 90 | path.lineTo(brightRectF.left, brightRectF.top); 91 | //E 92 | path.lineTo(0, brightRectF.top); 93 | //F 94 | path.lineTo(0, decorView.getHeight()); 95 | //F 96 | path.lineTo(decorView.getWidth(), decorView.getHeight()); 97 | //H 98 | path.lineTo(decorView.getWidth(), 0); 99 | //I 100 | path.lineTo(0, 0); 101 | //E 102 | path.lineTo(0, brightRectF.top); 103 | //A 104 | path.lineTo(brightRectF.left, brightRectF.top); 105 | //闭合曲线 106 | path.close(); 107 | canvas.drawPath(path, paint); 108 | } 109 | 110 | /** 111 | * 添加不加蒙版的View 112 | * 113 | * @param view 视图 114 | */ 115 | public void attachView(View view) { 116 | if (!showing) { 117 | this.brightView = view; 118 | //没有添加,才可以添加 119 | initMask(); 120 | } 121 | } 122 | 123 | /** 124 | * 移除遮罩 125 | */ 126 | public void removeMask() { 127 | if (!showing) { 128 | //没有添加 129 | return; 130 | } 131 | //使用动画消失 132 | AlphaAnimation animation = new AlphaAnimation(1f, 0f); 133 | animation.setDuration(500); 134 | animation.setAnimationListener(new Animation.AnimationListener() { 135 | @Override 136 | public void onAnimationStart(Animation animation) { 137 | } 138 | 139 | @Override 140 | public void onAnimationEnd(Animation animation) { 141 | ViewParent parent = MaskView.this.getParent(); 142 | if (parent != null && parent instanceof ViewGroup) { 143 | ((ViewGroup) parent).removeView(MaskView.this); 144 | showing = false; 145 | } 146 | } 147 | 148 | @Override 149 | public void onAnimationRepeat(Animation animation) { 150 | 151 | } 152 | }); 153 | startAnimation(animation); 154 | 155 | } 156 | 157 | /** 158 | * 初始化不加遮罩view的位置 159 | */ 160 | private void initMask() { 161 | int[] location = new int[2]; 162 | //获取view在屏幕上的坐标 163 | brightView.getLocationOnScreen(location); 164 | brightRectF = new RectF(location[0], location[1], location[0] + brightView.getWidth(), 165 | location[1] + brightView.getHeight()); 166 | //添加蒙版到Activity之上 167 | decorView.addView(this); 168 | invalidate(); 169 | showing = true; 170 | } 171 | 172 | /** 173 | * 是否已经添加 174 | * 175 | * @return 176 | */ 177 | public boolean isShowing() { 178 | return showing; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /PhoneEditText.java: -------------------------------------------------------------------------------- 1 | package com.aiche.recharge.view; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.AppCompatEditText; 5 | import android.text.Editable; 6 | import android.text.InputFilter; 7 | import android.text.Selection; 8 | import android.text.TextWatcher; 9 | import android.text.method.DigitsKeyListener; 10 | import android.util.AttributeSet; 11 | 12 | /** 13 | * 文件名: PhoneEditText 14 | * 创建时间: 2018/4/17 on 14:51 15 | * 描述: TODO 一个自动分割手机号码的输入框 16 | * eg:132 xxxx xxx 17 | *

当然中间这个分割符号你可以自己定义 18 | * 修改这个属性的值即可{@link flag},(目前支持分隔符只占一个长度) 19 | *

20 | * 21 | * @author 阿钟 22 | */ 23 | 24 | public class PhoneEditText extends AppCompatEditText implements TextWatcher { 25 | /** 26 | * 分割符 27 | */ 28 | private final String flag = " "; 29 | 30 | public PhoneEditText(Context context) { 31 | super(context); 32 | init(); 33 | } 34 | 35 | public PhoneEditText(Context context, AttributeSet attrs) { 36 | super(context, attrs); 37 | init(); 38 | } 39 | 40 | private void init() { 41 | //设置输入框允许输入的类型(正则) 42 | setKeyListener(DigitsKeyListener.getInstance("0123456789")); 43 | //设置输入字符 44 | addTextChangedListener(this); 45 | InputFilter.LengthFilter filter = new InputFilter.LengthFilter(13); 46 | setFilters(new InputFilter[]{filter}); 47 | } 48 | 49 | @Override 50 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 51 | 52 | } 53 | 54 | @Override 55 | public void onTextChanged(CharSequence s, int start, int before, int count) { 56 | StringBuilder sb = new StringBuilder(s); 57 | int length = sb.length(); 58 | if (before == 0) { 59 | //输入内容 60 | if (length == 4) { 61 | sb.insert(3, flag); 62 | setText(sb.toString()); 63 | } else if (length == 9) { 64 | sb.insert(8, flag); 65 | setText(sb.toString()); 66 | } 67 | } else if (length > 0) { 68 | //删除内容 69 | if (length == 4) { 70 | sb.deleteCharAt(3); 71 | setText(sb.toString()); 72 | } else if (length == 9) { 73 | sb.deleteCharAt(8); 74 | setText(sb.toString()); 75 | } 76 | } 77 | Selection.setSelection(getAllText(), sb.length()); 78 | } 79 | 80 | /** 81 | * 获取实际的手机号 82 | * 83 | * @return 没有分割符号的字符串 84 | */ 85 | public String getRealPhone() { 86 | String editable = super.getText().toString(); 87 | String number = editable.replaceAll(flag, ""); 88 | return number; 89 | } 90 | 91 | @Override 92 | public void setText(CharSequence text, BufferType type) { 93 | if (text.length() == 11) { 94 | StringBuilder sb = new StringBuilder(text); 95 | sb.insert(3, flag).insert(8, flag); 96 | setText(sb.toString()); 97 | } else { 98 | super.setText(text, type); 99 | } 100 | } 101 | 102 | public Editable getAllText() { 103 | return super.getText(); 104 | } 105 | 106 | @Override 107 | public void afterTextChanged(Editable s) { 108 | 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 个人整理的一个工具类的集合,包括一些自定义的View 2 | 3 | ### ● TimeClock.java——>自定义时钟View——>[使用方法](http://blog.csdn.net/a_zhon/article/details/53027501)——>效果图 4 | 5 | 6 | 7 | ### ● LogUtil.java——>Log日志工具类 8 | 9 | ### ● ScratchCard.java——>刮刮卡效果——>效果图 10 | 11 | 12 | 13 | ### ● LoadingView.java——>菊花进度——>[使用方法](http://blog.csdn.net/a_zhon/article/details/53143034)——>效果图 14 | 15 | 16 | 17 | ### ● WordsNavigation.java——>联系人列表字母索引——>[使用方法](http://blog.csdn.net/a_zhon/article/details/53214849)——>效果图 18 | 19 | 20 | 21 | ### ● BaseRecyclerAdapter.java——>RecyclerView适配器——>[使用方法](http://blog.csdn.net/a_zhon/article/details/66971369) 22 | 23 | ``` 24 | public class MyAdapter extends BaseRecyclerAdapter { 25 | 26 | public MyAdapter(int layoutResId, List data) { 27 | super(layoutResId, data); 28 | } 29 | 30 | @Override 31 | protected void bindTheData(BaseRecyclerAdapter.BaseViewHolder holder, DataBean data, int position) { 32 | holder.setText(android.R.id.text1, data.getName()); 33 | } 34 | } 35 | ``` 36 | 37 | --- 38 | 39 | ``` 40 | MyAdapter adapter = new MyAdapter(android.R.layout.simple_list_item_1, list); 41 | //添加头布局 42 | adapter.addHeadView(R.layout.head_view); 43 | //添加尾布局 44 | adapter.addFootView(R.layout.foot_view); 45 | recyclerView.setAdapter(adapter); 46 | adapter.setClickListener(new BaseRecyclerAdapter.onItemClickListener() { 47 | @Override 48 | public void onItemClick(int position, View v) { 49 | Toast.makeText(MainActivity.this, "点击 position = " + position, Toast.LENGTH_SHORT).show(); 50 | } 51 | }); 52 | ``` 53 | 54 | ### ● ScreenSizeUtils.java——>获取手机屏幕大小 55 | 56 | ### ● SharePreUtils.java——>SharedPreferences数据存储工具类 57 | 58 | ### ● AmountEditText.java——>只允许输入两位小数的输入框 59 | 60 | ``` 61 | //添加文本改变监听 62 | editeText.setListener(new AmountEditText.OnTextChangeListener() { 63 | @Override 64 | public void onTextChanged(String s) { 65 | //回调 66 | } 67 | }); 68 | ``` 69 | 70 | ### ● TimeUtils.java——>时间戳转化为年月日格式 71 | 72 | ### ● WaveView.java——>水波球——>[使用方法](http://blog.csdn.net/a_zhon/article/details/77842615)——>效果图 73 | 74 | 75 | 76 | ### ● ActionBarToast.java——>顶部消息弹框——>[使用方法](http://blog.csdn.net/a_zhon/article/details/78988653)——>效果图 77 | 78 | 79 | 80 | ``` 81 | ActionBarToast barToast = new ActionBarToast(this); 82 | barToast.showToast(ActionBarToast.LENGTH_SHORT); 83 | //barTost.cancel(); 84 | ``` 85 | 86 | ### ● MaskView.java——>观看视频开灯/关灯效果——>[使用方法](http://blog.csdn.net/a_zhon/article/details/78988653)——>效果图 87 | 88 | 89 | 90 | ``` 91 | MaskView maskView = new MaskView(this); 92 | maskView.attachView(view); 93 | //maskView.removeMask(); 94 | ``` 95 | 96 | ### ● PhoneEditText.java——>手机号码输入框自动分割——>效果图 97 | 98 | 99 | 100 | ### ● CustomTouchView.java——>自定义滚动View——>效果图 101 | 102 | 103 | -------------------------------------------------------------------------------- /ScratchCard.java: -------------------------------------------------------------------------------- 1 | package com.zsy.lambda; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Canvas; 7 | import android.graphics.Color; 8 | import android.graphics.Paint; 9 | import android.graphics.Path; 10 | import android.graphics.PorterDuff; 11 | import android.graphics.PorterDuffXfermode; 12 | import android.util.AttributeSet; 13 | import android.view.MotionEvent; 14 | import android.view.View; 15 | 16 | /* 17 | * 项目名: AndroidHeros 18 | * 包名: com.zsy.androidheros.view 19 | * 文件名: ScratchCard 20 | * 创建者: ZSY 21 | * 创建时间: 2016/11/4 17:15 22 | * 描述: 刮刮卡效果 23 | */ 24 | public class ScratchCard extends View { 25 | 26 | //手指触摸屏幕, 27 | private Paint paint; 28 | private Path path; 29 | //隐藏的背景图片 30 | private Bitmap bgBitmap; 31 | //覆盖在上层的灰色图层 32 | private Bitmap fgBitmap; 33 | private Canvas canvas; 34 | 35 | public ScratchCard(Context context) { 36 | super(context); 37 | initPaint(); 38 | } 39 | 40 | public ScratchCard(Context context, AttributeSet attrs) { 41 | super(context, attrs); 42 | initPaint(); 43 | } 44 | 45 | public ScratchCard(Context context, AttributeSet attrs, int defStyleAttr) { 46 | super(context, attrs, defStyleAttr); 47 | } 48 | 49 | private void initPaint() { 50 | paint = new Paint(); 51 | paint.setAlpha(0); 52 | //在已有的图像上绘图将会在其上面添加一层新的图层,如果新图层的paint是不透明的,那么它将遮挡住下面的paint; 53 | //如果新图层它是部分透明的,那么它不透明的地方将会被染上下面的颜色 54 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); 55 | paint.setStyle(Paint.Style.STROKE); 56 | //设置笔触和连接处能更加圆滑 57 | paint.setStrokeJoin(Paint.Join.ROUND); 58 | paint.setStrokeCap(Paint.Cap.ROUND); 59 | 60 | paint.setStrokeWidth(60); 61 | path = new Path(); 62 | bgBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.a_zhon); 63 | fgBitmap = Bitmap.createBitmap(bgBitmap.getWidth(), bgBitmap.getHeight(), Bitmap.Config.ARGB_8888); 64 | canvas = new Canvas(fgBitmap); 65 | //在图层上绘制一层颜色 66 | canvas.drawColor(Color.parseColor("#1dcdef")); 67 | } 68 | 69 | @Override 70 | protected void onDraw(Canvas canvas) { 71 | super.onDraw(canvas); 72 | //绘制背景图层 73 | canvas.drawBitmap(bgBitmap, 0, 0, null); 74 | //绘制遮罩图层 75 | canvas.drawBitmap(fgBitmap, 0, 0, null); 76 | } 77 | 78 | @Override 79 | public boolean onTouchEvent(MotionEvent event) { 80 | switch (event.getAction()) { 81 | case MotionEvent.ACTION_DOWN: 82 | path.reset(); 83 | path.moveTo(event.getX(), event.getY()); 84 | //实现点击檫除 85 | path.lineTo(event.getX() + 1, event.getY() + 1); 86 | break; 87 | case MotionEvent.ACTION_MOVE: 88 | path.lineTo(event.getX(), event.getY()); 89 | break; 90 | case MotionEvent.ACTION_UP: 91 | break; 92 | } 93 | canvas.drawPath(path, paint); 94 | invalidate(); 95 | return true; 96 | 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /ScreenSizeUtils.java: -------------------------------------------------------------------------------- 1 | package cn.hjtech.pigeon.common.utils; 2 | 3 | import android.content.Context; 4 | import android.util.DisplayMetrics; 5 | import android.view.WindowManager; 6 | 7 | /** 8 | * 文件名: SharePreUtils 9 | * 创建时间: 2017/05/24 on 17:58 10 | * 描述: 11 | * 1.获取手机屏幕的宽高 12 | * 2.dip 与 px 之间的转换 13 | * 14 | * @see android.support.annotation.Px 15 | */ 16 | 17 | public class ScreenSizeUtils { 18 | 19 | private static ScreenSizeUtils instance = null; 20 | private int screenWidth, screenHeight; 21 | 22 | public static ScreenSizeUtils getInstance(Context mContext) { 23 | if (instance == null) { 24 | synchronized (ScreenSizeUtils.class) { 25 | if (instance == null) 26 | instance = new ScreenSizeUtils(mContext); 27 | } 28 | } 29 | return instance; 30 | } 31 | 32 | private ScreenSizeUtils(Context mContext) { 33 | WindowManager manager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 34 | DisplayMetrics dm = new DisplayMetrics(); 35 | manager.getDefaultDisplay().getMetrics(dm); 36 | // 获取屏幕分辨率宽度 37 | screenWidth = dm.widthPixels; 38 | // 获取屏幕分辨率高度 39 | screenHeight = dm.heightPixels; 40 | } 41 | 42 | //获取屏幕宽度 43 | public int getScreenWidth() { 44 | return screenWidth; 45 | } 46 | 47 | //获取屏幕高度 48 | public int getScreenHeight() { 49 | return screenHeight; 50 | } 51 | 52 | /** 53 | * 根据手机的分辨率从 dip 的单位 转成为 px(像素) 54 | * 55 | * @return px 56 | */ 57 | private int dip2px(Context context, float dpValue) { 58 | final float scale = context.getResources().getDisplayMetrics().density; 59 | return (int) (dpValue * scale + 0.5f); 60 | } 61 | 62 | /** 63 | * 根据手机的分辨率从 px(像素) 的单位 转成为 dp 64 | */ 65 | public static int px2dip(Context context, float pxValue) { 66 | final float scale = context.getResources().getDisplayMetrics().density; 67 | return (int) (pxValue / scale + 0.5f); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /SharePreUtils.java: -------------------------------------------------------------------------------- 1 | package cn.hjtech.pigeon.common.utils; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.File; 8 | import java.io.FileInputStream; 9 | import java.io.InputStreamReader; 10 | 11 | /* 12 | * 文件名: SharePreUtils 13 | * 创建时间: 2017/05/24 on 11:58 14 | * 描述: SharedPreferences存取工具类 15 | */ 16 | 17 | public class SharePreUtils { 18 | 19 | //配置文件,文件名 20 | public static final String SHARE_NAME = "config"; 21 | 22 | /** 23 | * 万能put 24 | * 25 | * @param mContext 上下文 26 | * @param key 键 27 | * @param values 值 28 | */ 29 | public static void put(Context mContext, String key, Object values) { 30 | if (values instanceof String) { 31 | putString(mContext, key, (String) values); 32 | } else if (values instanceof Integer) { 33 | putInt(mContext, key, (Integer) values); 34 | } else if (values instanceof Boolean) { 35 | putBoolean(mContext, key, (Boolean) values); 36 | } else { 37 | //除去上面三个类型,都存储为String 38 | putString(mContext, key, String.valueOf(values)); 39 | } 40 | 41 | } 42 | 43 | /** 44 | * 存字符串 45 | * 46 | * @param mContext 47 | * @param key 48 | * @param values 49 | */ 50 | public static void putString(Context mContext, String key, String values) { 51 | SharedPreferences sp = mContext.getSharedPreferences(SHARE_NAME, Context.MODE_PRIVATE); 52 | sp.edit().putString(key, values).commit(); 53 | } 54 | 55 | 56 | /** 57 | * 取字符串 58 | * 59 | * @param mContext 上下文 60 | * @param key 键 61 | * @param values 默认值 62 | * @return 取出的值 63 | */ 64 | public static String getString(Context mContext, String key, String values) { 65 | SharedPreferences sp = mContext.getSharedPreferences(SHARE_NAME, Context.MODE_PRIVATE); 66 | return sp.getString(key, values); 67 | } 68 | 69 | 70 | /** 71 | * 存布尔值 72 | * 73 | * @param mContext 上下文 74 | * @param key 键 75 | * @param values 值 76 | */ 77 | public static void putBoolean(Context mContext, String key, boolean values) { 78 | SharedPreferences sp = mContext.getSharedPreferences(SHARE_NAME, Context.MODE_PRIVATE); 79 | sp.edit().putBoolean(key, values).commit(); 80 | } 81 | 82 | /** 83 | * 取布尔值 84 | * 85 | * @param mContext 上下文 86 | * @param key 键 87 | * @param values 默认值 88 | * @return true/false 89 | */ 90 | public static boolean getBoolean(Context mContext, String key, boolean values) { 91 | SharedPreferences sp = mContext.getSharedPreferences(SHARE_NAME, Context.MODE_PRIVATE); 92 | return sp.getBoolean(key, values); 93 | } 94 | 95 | /** 96 | * 存int值 97 | * 98 | * @param mContext 上下文 99 | * @param key 键 100 | * @param values 值 101 | */ 102 | public static void putInt(Context mContext, String key, int values) { 103 | SharedPreferences sp = mContext.getSharedPreferences(SHARE_NAME, Context.MODE_PRIVATE); 104 | sp.edit().putInt(key, values).commit(); 105 | } 106 | 107 | /** 108 | * 取int值 109 | * 110 | * @param mContext 上下文 111 | * @param key 键 112 | * @param values 默认值 113 | * @return 114 | */ 115 | public static int getInt(Context mContext, String key, int values) { 116 | SharedPreferences sp = mContext.getSharedPreferences(SHARE_NAME, Context.MODE_PRIVATE); 117 | return sp.getInt(key, values); 118 | } 119 | 120 | /** 121 | * 删除一条字段 122 | * 123 | * @param mContext 上下文 124 | * @param key 键 125 | */ 126 | public static void deleShare(Context mContext, String key) { 127 | SharedPreferences sp = mContext.getSharedPreferences(SHARE_NAME, Context.MODE_PRIVATE); 128 | //单个清理 129 | sp.edit().remove(key).commit(); 130 | } 131 | 132 | /** 133 | * 删除全部数据 134 | * 135 | * @param mContext 上下文 136 | */ 137 | public static void deleShareAll(Context mContext) { 138 | SharedPreferences sp = mContext.getSharedPreferences(SHARE_NAME, Context.MODE_PRIVATE); 139 | //全部清理 140 | sp.edit().clear().commit(); 141 | } 142 | 143 | /** 144 | * 查看SharedPreferences的内容 145 | */ 146 | public static String lookSharePre(Context context) { 147 | try { 148 | FileInputStream stream = new FileInputStream(new File("/data/data/" + context.getPackageName() + "/shared_prefs", "config.xml")); 149 | BufferedReader bff = new BufferedReader(new InputStreamReader(stream)); 150 | String line; 151 | StringBuilder sb = new StringBuilder(); 152 | while ((line = bff.readLine()) != null) { 153 | sb.append(line); 154 | sb.append("\n"); 155 | } 156 | return sb.toString(); 157 | } catch (Exception e) { 158 | e.printStackTrace(); 159 | } 160 | return "未找到当前配置文件!"; 161 | } 162 | } -------------------------------------------------------------------------------- /TimeClock.java: -------------------------------------------------------------------------------- 1 | package com.zsy.androidheros.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | 10 | import java.text.SimpleDateFormat; 11 | import java.util.Date; 12 | 13 | /* 14 | * 项目名: AndroidHeros 15 | * 包名: com.zsy.androidheros.view 16 | * 文件名: TimeClock 17 | * 创建者: 阿钟 18 | * 创建时间: 2016/11/3 12:54 19 | * 描述: 绘制时钟 20 | */ 21 | public class TimeClock extends View { 22 | 23 | //外圆画笔 24 | private Paint paint; 25 | //文字画笔 26 | private Paint paintNum; 27 | //时钟画笔 28 | private Paint paintHour; 29 | //分钟画笔 30 | private Paint paintMinute; 31 | //秒钟画笔 32 | private Paint paintSecond; 33 | //外圆圆心 34 | private float x, y; 35 | //外圆半径 36 | private int r; 37 | 38 | public TimeClock(Context context) { 39 | super(context); 40 | initPaint(); 41 | } 42 | 43 | public TimeClock(Context context, AttributeSet attrs) { 44 | super(context, attrs); 45 | initPaint(); 46 | } 47 | 48 | public TimeClock(Context context, AttributeSet attrs, int defStyleAttr) { 49 | super(context, attrs, defStyleAttr); 50 | initPaint(); 51 | } 52 | 53 | 54 | private void initPaint() { 55 | paint = new Paint(); 56 | paint.setColor(Color.BLACK); 57 | paint.setAntiAlias(true); 58 | paint.setStrokeWidth(3); 59 | paint.setStyle(Paint.Style.STROKE); 60 | 61 | paintNum = new Paint(); 62 | paintNum.setColor(Color.BLACK); 63 | paintNum.setAntiAlias(true); 64 | paintNum.setTextSize(35); 65 | paintNum.setStyle(Paint.Style.STROKE); 66 | paintNum.setTextAlign(Paint.Align.CENTER); 67 | 68 | paintSecond = new Paint(); 69 | paintSecond.setColor(Color.RED); 70 | paintSecond.setAntiAlias(true); 71 | paintSecond.setStrokeWidth(5); 72 | paintSecond.setStyle(Paint.Style.FILL); 73 | 74 | paintMinute = new Paint(); 75 | paintMinute.setColor(Color.BLACK); 76 | paintMinute.setAntiAlias(true); 77 | paintMinute.setStrokeWidth(8); 78 | paintMinute.setStyle(Paint.Style.FILL); 79 | 80 | paintHour = new Paint(); 81 | paintHour.setColor(Color.BLACK); 82 | paintHour.setAntiAlias(true); 83 | paintHour.setStrokeWidth(13); 84 | paintHour.setStyle(Paint.Style.FILL); 85 | } 86 | 87 | @Override 88 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 89 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 90 | int height = getMeasuredHeight(); 91 | int width = getMeasuredWidth(); 92 | x = width / 2; 93 | y = height / 2; 94 | r = (int) x - 5; 95 | } 96 | 97 | @Override 98 | protected void onDraw(Canvas canvas) { 99 | super.onDraw(canvas); 100 | //绘制外圆 101 | canvas.drawCircle(x, y, r, paint); 102 | 103 | //绘制圆心 104 | canvas.drawCircle(x, y, 15, paintMinute); 105 | 106 | //绘制刻度 107 | drawLines(canvas); 108 | 109 | //绘制整点 110 | drawText(canvas); 111 | 112 | try { 113 | initCurrentTime(canvas); 114 | } catch (Exception e) { 115 | e.printStackTrace(); 116 | } 117 | //每隔1s刷新界面 118 | postInvalidateDelayed(1000); 119 | } 120 | 121 | 122 | /** 123 | * 绘制时钟刻度和分钟刻度 124 | * 125 | * @param canvas 画布 126 | */ 127 | private void drawLines(Canvas canvas) { 128 | for (int i = 0; i < 60; i++) { 129 | if (i % 5 == 0) { 130 | //绘制整点刻度 131 | paint.setStrokeWidth(8); 132 | canvas.drawLine(x, y - r, x, y - r + 40, paint); 133 | } else { 134 | //绘制分钟刻度 135 | paint.setStrokeWidth(3); 136 | canvas.drawLine(x, y - r, x, y - r + 30, paint); 137 | } 138 | //绕着(x,y)旋转6° 139 | canvas.rotate(6, x, y); 140 | } 141 | } 142 | 143 | /** 144 | * 绘制整点数字 145 | * 146 | * @param canvas 画布 147 | */ 148 | private void drawText(Canvas canvas) { 149 | // 获取文字高度用于设置文本垂直居中 150 | float textSize = (paintNum.getFontMetrics().bottom - paintNum.getFontMetrics().top); 151 | // 数字离圆心的距离,40为刻度的长度,20文字大小 152 | int distance = r - 40 - 20; 153 | // 数字的坐标(a,b) 154 | float a, b; 155 | // 每30°写一个数字 156 | for (int i = 0; i < 12; i++) { 157 | a = (float) (distance * Math.sin(i * 30 * Math.PI / 180) + x); 158 | b = (float) (y - distance * Math.cos(i * 30 * Math.PI / 180)); 159 | if (i == 0) { 160 | canvas.drawText("12", a, b + textSize / 3, paintNum); 161 | } else { 162 | canvas.drawText(String.valueOf(i), a, b + textSize / 3, paintNum); 163 | } 164 | } 165 | } 166 | 167 | /** 168 | * 获取当前系统时间 169 | * 170 | * @param canvas 画布 171 | */ 172 | private void initCurrentTime(Canvas canvas) { 173 | //获取系统当前时间 174 | SimpleDateFormat format = new SimpleDateFormat("HH-mm-ss"); 175 | String time = format.format(new Date(System.currentTimeMillis())); 176 | String[] split = time.split("-"); 177 | int hour = Integer.parseInt(split[0]); 178 | int minute = Integer.parseInt(split[1]); 179 | int second = Integer.parseInt(split[2]); 180 | //时针走过的角度 181 | int hourAngle = hour * 30 + minute / 2; 182 | //分针走过的角度 183 | int minuteAngle = minute * 6 + second / 10; 184 | //秒针走过的角度 185 | int secondAngle = second * 6; 186 | 187 | //绘制时钟,以12整点为0°参照点 188 | canvas.rotate(hourAngle, x, y); 189 | canvas.drawLine(x, y, x, y - r + 150, paintHour); 190 | canvas.save(); 191 | canvas.restore(); 192 | //这里画好了时钟,我们需要再将画布转回来,继续以12整点为0°参照点 193 | canvas.rotate(-hourAngle, x, y); 194 | 195 | //绘制分钟 196 | canvas.rotate(minuteAngle, x, y); 197 | canvas.drawLine(x, y, x, y - r + 60, paintMinute); 198 | canvas.save(); 199 | canvas.restore(); 200 | //这里同上 201 | canvas.rotate(-minuteAngle, x, y); 202 | 203 | //绘制秒钟 204 | canvas.rotate(secondAngle, x, y); 205 | canvas.drawLine(x, y, x, y - r + 20, paintSecond); 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /TimeUtils.java: -------------------------------------------------------------------------------- 1 | package com.hjtech.baselib.utils; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | 6 | /* 7 | * 文件名: TimeUtils 8 | * 创建者: 阿钟 9 | * 创建时间: 2017/8/22 on 15:12 10 | * 描述: TODO 时间格式化工具类 11 | */ 12 | public class TimeUtils { 13 | 14 | /** 15 | * 将时间戳转换为时间 16 | * 17 | * @param time 时间戳 18 | * @return yyyy-MM-dd 19 | */ 20 | public static String yyyyMMdd(long time) { 21 | if (time == 0) 22 | return ""; 23 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); 24 | Date date = new Date(time); 25 | return format.format(date); 26 | } 27 | 28 | /*** 29 | * 时间戳转换为时间 30 | * 31 | * @param time 时间戳 32 | * @return HH:mm:ss 33 | */ 34 | public static String HHmmss(long time) { 35 | if (time == 0) 36 | return ""; 37 | SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss"); 38 | Date date = new Date(time); 39 | return format.format(date); 40 | } 41 | 42 | /*** 43 | * 时间戳转换为时间 44 | * 45 | * @param time 时间戳 46 | * @return yyyy-MM-dd HH:mm 47 | */ 48 | public static String yyyyMMddHHmm(long time) { 49 | if (time == 0) 50 | return ""; 51 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm"); 52 | Date date = new Date(time); 53 | return format.format(date); 54 | } 55 | 56 | /*** 57 | * 时间戳转换为时间 58 | * 59 | * @param time 时间戳 60 | * @return yyyy-MM-dd HH:mm:ss 61 | */ 62 | public static String yyyyMMddHHmmss(long time) { 63 | if (time == 0) 64 | return ""; 65 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 66 | Date date = new Date(time); 67 | return format.format(date); 68 | } 69 | 70 | /*** 71 | * 时间戳转换为时间 72 | * 73 | * @param time 时间戳 74 | * @return MM-dd HH:mm 75 | */ 76 | public static String MMddHHmm(long time) { 77 | if (time == 0) { 78 | return ""; 79 | } 80 | SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm"); 81 | Date date = new Date(time); 82 | return format.format(date); 83 | } 84 | 85 | /*** 86 | * 时间戳转换为时间 87 | * 88 | * @param time 时间戳 89 | * @return MM-dd HH:mm:ss 90 | */ 91 | public static String MMddHHmmss(long time) { 92 | if (time == 0) { 93 | return ""; 94 | } 95 | SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss"); 96 | Date date = new Date(time); 97 | return format.format(date); 98 | } 99 | 100 | /*** 101 | * 时间转换为时间戳 102 | * 103 | * @param time 时间 104 | * @param format 格式 例如:yyyy-MM-dd HH:mm:ss 105 | * @return 时间戳 106 | */ 107 | public static long toTimeStamp(String time, String format) { 108 | SimpleDateFormat format = new SimpleDateFormat(format); 109 | return format.parse(time).getTime(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /WaveView.java: -------------------------------------------------------------------------------- 1 | package com.azhon.test; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.Point; 10 | import android.graphics.Rect; 11 | import android.support.annotation.Nullable; 12 | import android.util.AttributeSet; 13 | import android.view.View; 14 | import android.view.animation.LinearInterpolator; 15 | 16 | /* 17 | * 文件名: WaveView 18 | * 创建者: ZSY 19 | * 创建时间: 2017/8/29 on 09:10 20 | * 描述: TODO 水波纹view 剩余流量球 21 | */ 22 | public class WaveView extends View { 23 | 24 | /*画布宽度*/ 25 | private int width; 26 | /*画布高度*/ 27 | private int height; 28 | /*sin曲线画笔*/ 29 | private Paint paint; 30 | /*圆的画笔*/ 31 | private Paint textPaint; 32 | /*文本画笔*/ 33 | private Paint circlePaint; 34 | /*sin曲线的路径*/ 35 | private Path path; 36 | /*sin曲线 1/4个周期的宽度*/ 37 | private int cycle = 160; 38 | /*sin曲线振幅的高度*/ 39 | private int waveHeight = 80; 40 | /*sin曲线的起点*/ 41 | private Point startPoint; 42 | /*当前进度*/ 43 | private int progress; 44 | /*x轴平移量*/ 45 | private int translateX = 40; 46 | /*是否启用了动画设置进度*/ 47 | private boolean openAnimate = false; 48 | /*是否自增长*/ 49 | private boolean autoIncrement = true; 50 | 51 | public WaveView(Context context, @Nullable AttributeSet attrs) { 52 | super(context, attrs); 53 | init(context); 54 | } 55 | 56 | public WaveView(Context context) { 57 | super(context); 58 | init(context); 59 | } 60 | 61 | private void init(Context context) { 62 | path = new Path(); 63 | paint = new Paint(); 64 | paint.setAntiAlias(true); 65 | paint.setStrokeWidth(dip2px(context, 5)); 66 | paint.setStyle(Paint.Style.FILL); 67 | paint.setColor(Color.GREEN); 68 | 69 | circlePaint = new Paint(); 70 | circlePaint.setStrokeWidth(dip2px(context, 5)); 71 | circlePaint.setStyle(Paint.Style.STROKE); 72 | circlePaint.setAntiAlias(true); 73 | circlePaint.setColor(Color.parseColor("#FF4081")); 74 | 75 | textPaint = new Paint(); 76 | textPaint.setAntiAlias(true); 77 | textPaint.setTextSize(dip2px(context, 20)); 78 | textPaint.setColor(Color.BLACK); 79 | } 80 | 81 | @Override 82 | protected void onDraw(Canvas canvas) { 83 | super.onDraw(canvas); 84 | //裁剪画布为圆形 85 | Path circlePath = new Path(); 86 | circlePath.addCircle(width / 2, height / 2, width / 2, Path.Direction.CW); 87 | canvas.clipPath(circlePath); 88 | canvas.drawPaint(circlePaint); 89 | canvas.drawCircle(width / 2, height / 2, width / 2, circlePaint); 90 | //以下操作都是在这个圆形画布中操作 91 | 92 | //根据进度改变起点坐标的y值 93 | startPoint.y = (int) (height - (progress / 100.0 * height)); 94 | //起点 95 | path.moveTo(startPoint.x, startPoint.y); 96 | int j = 1; 97 | //循环绘制正弦曲线 循环一次半个周期 98 | for (int i = 1; i <= 8; i++) { 99 | if (i % 2 == 0) { 100 | //波峰 101 | path.quadTo(startPoint.x + (cycle * j), startPoint.y + waveHeight, 102 | startPoint.x + (cycle * 2) * i, startPoint.y); 103 | } else { 104 | //波谷 105 | path.quadTo(startPoint.x + (cycle * j), startPoint.y - waveHeight, 106 | startPoint.x + (cycle * 2) * i, startPoint.y); 107 | } 108 | j += 2; 109 | } 110 | //绘制封闭的曲线 111 | path.lineTo(width, height);//右下角 112 | path.lineTo(startPoint.x, height);//左下角 113 | path.lineTo(startPoint.x, startPoint.y);//起点 114 | path.close(); 115 | canvas.drawPath(path, paint); 116 | 117 | drawText(canvas, textPaint, progress + "%"); 118 | //判断是不是平移完了一个周期 119 | if (startPoint.x + translateX >= 0) { 120 | //满了一个周期则恢复默认起点继续平移 121 | startPoint.x = -cycle * 4; 122 | } 123 | //每次波形的平移量 40 124 | startPoint.x += translateX; 125 | if (autoIncrement) { 126 | if (progress >= 100) { 127 | progress = 0; 128 | } else { 129 | progress++; 130 | } 131 | } 132 | path.reset(); 133 | if (!openAnimate) { 134 | postInvalidateDelayed(150); 135 | } 136 | } 137 | 138 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 139 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 140 | //获取view的宽度 141 | width = getViewSize(400, widthMeasureSpec); 142 | //获取view的高度 143 | height = getViewSize(400, heightMeasureSpec); 144 | //默认从屏幕外先绘制3/4个周期 使得波峰在圆中间 145 | startPoint = new Point(-cycle * 3, height / 2); 146 | } 147 | 148 | 149 | private int getViewSize(int defaultSize, int measureSpec) { 150 | int viewSize = defaultSize; 151 | //获取测量模式 152 | int mode = MeasureSpec.getMode(measureSpec); 153 | //获取大小 154 | int size = MeasureSpec.getSize(measureSpec); 155 | switch (mode) { 156 | case MeasureSpec.UNSPECIFIED: //如果没有指定大小,就设置为默认大小 157 | viewSize = defaultSize; 158 | break; 159 | case MeasureSpec.AT_MOST: //如果测量模式是最大取值为size 160 | //我们将大小取最大值,你也可以取其他值 161 | viewSize = size; 162 | break; 163 | case MeasureSpec.EXACTLY: //如果是固定的大小,那就不要去改变它 164 | viewSize = size; 165 | break; 166 | } 167 | return viewSize; 168 | } 169 | 170 | /** 171 | * 绘制文字 172 | * 173 | * @param canvas 画布 174 | * @param paint 画笔 175 | * @param text 画的文字 176 | */ 177 | private void drawText(Canvas canvas, Paint paint, String text) { 178 | //画布的大小 179 | Rect targetRect = new Rect(0, 0, width, height); 180 | Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); 181 | int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2; 182 | // 下面这行是实现水平居中,drawText对应改为传入targetRect.centerX() 183 | paint.setTextAlign(Paint.Align.CENTER); 184 | canvas.drawText(text, targetRect.centerX(), baseline, paint); 185 | } 186 | 187 | /** 188 | * 根据手机的分辨率从 dip 的单位 转成为 px(像素) 189 | */ 190 | public int dip2px(Context context, float dpValue) { 191 | final float scale = context.getResources().getDisplayMetrics().density; 192 | return (int) (dpValue * scale + 0.5f); 193 | } 194 | 195 | /** 196 | * 设置振幅高度 197 | * 198 | * @param waveHeight 振幅 199 | */ 200 | public void setWaveHeight(int waveHeight) { 201 | this.waveHeight = waveHeight; 202 | invalidate(); 203 | } 204 | 205 | /** 206 | * 设置sin曲线 1/4个周期的宽度 207 | * 208 | * @param cycle 1/4个周期的宽度 209 | */ 210 | public void setCycle(int cycle) { 211 | this.cycle = cycle; 212 | invalidate(); 213 | } 214 | 215 | /** 216 | * 设置当前进度 217 | * 218 | * @param progress 进度 219 | */ 220 | public void setProgress(int progress) { 221 | if (progress > 100 || progress < 0) 222 | throw new RuntimeException(getClass().getName() + "请设置[0,100]之间的值"); 223 | this.progress = progress; 224 | autoIncrement = false; 225 | invalidate(); 226 | } 227 | 228 | /** 229 | * 设置x轴移动量 230 | * 231 | * @param translateX 默认40 232 | */ 233 | public void setTranslateX(int translateX) { 234 | this.translateX = translateX; 235 | } 236 | 237 | /** 238 | * 通过动画设置当前进度 239 | * 240 | * @param progress 进度 <=100 241 | * @param duration 动画时长 242 | */ 243 | public void setProgress(final int progress, int duration) { 244 | if (progress > 100 || progress < 0) 245 | throw new RuntimeException(getClass().getName() + "请设置[0,100]之间的值"); 246 | autoIncrement = false; 247 | openAnimate = true; 248 | ValueAnimator progressAnimator = ValueAnimator.ofInt(0, progress); 249 | progressAnimator.setDuration(duration); 250 | progressAnimator.setTarget(progress); 251 | progressAnimator.setInterpolator(new LinearInterpolator()); 252 | progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 253 | @Override 254 | public void onAnimationUpdate(ValueAnimator animation) { 255 | WaveView.this.progress = (int) animation.getAnimatedValue(); 256 | if (WaveView.this.progress == progress) 257 | openAnimate = false; 258 | invalidate(); 259 | } 260 | }); 261 | progressAnimator.start(); 262 | } 263 | 264 | public int getProgress() { 265 | return progress; 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /WordsNavigation.java: -------------------------------------------------------------------------------- 1 | package com.zsy.words.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Rect; 8 | import android.graphics.Typeface; 9 | import android.util.AttributeSet; 10 | import android.view.MotionEvent; 11 | import android.view.View; 12 | 13 | /* 14 | * 文件名: WordsNavigation 15 | * 创建者: ZSY 16 | * 创建时间: 2016/11/17 15:34 17 | * 描述: 实现手机联系人列表字母导航 18 | */ 19 | public class WordsNavigation extends View { 20 | 21 | /*绘制的列表导航字母*/ 22 | private String words[] = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", 23 | "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "#"}; 24 | /*字母画笔*/ 25 | private Paint wordsPaint; 26 | /*字母背景画笔*/ 27 | private Paint bgPaint; 28 | /*每一个字母的宽度*/ 29 | private int itemWidth; 30 | /*每一个字母的高度*/ 31 | private int itemHeight; 32 | /*手指按下的字母索引*/ 33 | private int touchIndex = 0; 34 | /*手指按下的字母改变接口*/ 35 | private onWordsChangeListener listener; 36 | 37 | public WordsNavigation(Context context) { 38 | super(context); 39 | init(context); 40 | } 41 | 42 | public WordsNavigation(Context context, AttributeSet attrs) { 43 | super(context, attrs); 44 | init(context); 45 | } 46 | 47 | /** 48 | * 初始化画笔 49 | */ 50 | private void init(Context context) { 51 | wordsPaint = new Paint(); 52 | wordsPaint.setColor(Color.parseColor("#F7F7F7")); 53 | wordsPaint.setAntiAlias(true); 54 | wordsPaint.setTextSize(dip2px(context,12)); 55 | wordsPaint.setTypeface(Typeface.DEFAULT_BOLD); 56 | 57 | bgPaint = new Paint(); 58 | bgPaint.setAntiAlias(true); 59 | bgPaint.setColor(Color.parseColor("#1dcdef")); 60 | } 61 | 62 | @Override 63 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 64 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 65 | itemWidth = getMeasuredWidth(); 66 | //使得边距好看一些 67 | int height = getMeasuredHeight() - 10; 68 | itemHeight = height / 27; 69 | } 70 | 71 | @Override 72 | protected void onDraw(Canvas canvas) { 73 | super.onDraw(canvas); 74 | for (int i = 0; i < words.length; i++) { 75 | //判断是不是我们按下的当前字母 76 | if (touchIndex == i) { 77 | //绘制文字圆形背景 78 | canvas.drawCircle(itemWidth / 2, itemHeight / 2 + i * itemHeight, 23, bgPaint); 79 | wordsPaint.setColor(Color.WHITE); 80 | } else { 81 | wordsPaint.setColor(Color.GRAY); 82 | } 83 | //获取文字的宽高 84 | Rect rect = new Rect(); 85 | wordsPaint.getTextBounds(words[i], 0, 1, rect); 86 | int wordWidth = rect.width(); 87 | //绘制字母 88 | float wordX = itemWidth / 2 - wordWidth / 2; 89 | float wordY = itemWidth / 2 + i * itemHeight; 90 | canvas.drawText(words[i], wordX, wordY, wordsPaint); 91 | } 92 | } 93 | 94 | /** 95 | * 当手指触摸按下的时候改变字母背景颜色 96 | */ 97 | @Override 98 | public boolean onTouchEvent(MotionEvent event) { 99 | switch (event.getAction()) { 100 | case MotionEvent.ACTION_DOWN: 101 | case MotionEvent.ACTION_MOVE: 102 | float y = event.getY(); 103 | //获得我们按下的是那个索引(字母) 104 | int index = (int) (y / itemHeight); 105 | if (index != touchIndex) 106 | touchIndex = index; 107 | //防止数组越界 108 | if (listener != null && 0 <= touchIndex && touchIndex <= words.length - 1) { 109 | //回调按下的字母 110 | listener.wordsChange(words[touchIndex]); 111 | } 112 | invalidate(); 113 | break; 114 | case MotionEvent.ACTION_UP: 115 | //手指抬起,不做任何操作 116 | break; 117 | } 118 | return true; 119 | } 120 | 121 | /** 122 | * 根据手机的分辨率从 dip 的单位 转成为 px(像素) 123 | */ 124 | public int dip2px(Context context, float dpValue) { 125 | final float scale = context.getResources().getDisplayMetrics().density; 126 | return (int) (dpValue * scale + 0.5f); 127 | } 128 | 129 | /** 130 | * 根据手机的分辨率从 px(像素) 的单位 转成为 dp 131 | */ 132 | public int px2dip(Context context, float pxValue) { 133 | final float scale = context.getResources().getDisplayMetrics().density; 134 | return (int) (pxValue / scale + 0.5f); 135 | } 136 | 137 | /*设置当前按下的是那个字母*/ 138 | public void setTouchIndex(String word) { 139 | for (int i = 0; i < words.length; i++) { 140 | if (words[i].equals(word)) { 141 | touchIndex = i; 142 | invalidate(); 143 | return; 144 | } 145 | } 146 | } 147 | 148 | /*手指按下了哪个字母的回调接口*/ 149 | public interface onWordsChangeListener { 150 | void wordsChange(String words); 151 | } 152 | 153 | /*设置手指按下字母改变监听*/ 154 | public void setOnWordsChangeListener(onWordsChangeListener listener) { 155 | this.listener = listener; 156 | } 157 | } 158 | 159 | -------------------------------------------------------------------------------- /effectImage/20161105231805323.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/MyUtils/60b1f5357e276f694b089d2cc09d60ac59b637df/effectImage/20161105231805323.gif -------------------------------------------------------------------------------- /effectImage/20161118122207199.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/MyUtils/60b1f5357e276f694b089d2cc09d60ac59b637df/effectImage/20161118122207199.png -------------------------------------------------------------------------------- /effectImage/custom_touch_view.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azhon/MyUtils/60b1f5357e276f694b089d2cc09d60ac59b637df/effectImage/custom_touch_view.gif --------------------------------------------------------------------------------