一.重写edittext 设置光标始终在最后
package com.lgd.factoryattendance.View;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.EditText;
/**
* @Author lgd
* @Date 2024/3/8 14:26
* 重写edittext 设置光标始终在最后
*/
public class LastInputEditText extends androidx.appcompat.widget.AppCompatEditText {
public LastInputEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public LastInputEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LastInputEditText(Context context) {
super(context);
}
@Override
protected void onSelectionChanged(int selStart, int selEnd) {
super.onSelectionChanged(selStart, selEnd);
//保证光标始终在最后面
if(selStart==selEnd){//防止不能多选
setSelection(getText().length());
}
}
}
二.自定义 换行 radioGroup
package com.lgd.factoryattendance.View;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.RadioGroup;
/**
* @Author lgd
* @Date 2024/4/9 10:39
* 自定义 换行 radioGroup
*/
public class LineFeedRadioGroup extends RadioGroup {
private static final String TAG = "RadioGroupEx";
public LineFeedRadioGroup(Context context) {
super(context);
}
public LineFeedRadioGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//调用ViewGroup的方法,测量子view
measureChildren(widthMeasureSpec, heightMeasureSpec);
//最大的宽
int maxWidth = 0;
//累计的高
int totalHeight = 0;
//当前这一行的累计行宽
int lineWidth = 0;
//当前这行的最大行高
int maxLineHeight = 0;
//用于记录换行前的行宽和行高
int oldHeight;
int oldWidth;
int count = getChildCount();
//假设 widthMode和heightMode都是AT_MOST
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
//得到这一行的最高
oldHeight = maxLineHeight;
//当前最大宽度
oldWidth = maxWidth;
int deltaX = child.getMeasuredWidth() + params.leftMargin + params.rightMargin;
if (lineWidth + deltaX + getPaddingLeft() + getPaddingRight() > widthSize) {//如果折行,height增加
//和目前最大的宽度比较,得到最宽。不能加上当前的child的宽,所以用的是oldWidth
maxWidth = Math.max(lineWidth, oldWidth);
//重置宽度
lineWidth = deltaX;
//累加高度
totalHeight += oldHeight;
//重置行高,当前这个View,属于下一行,因此当前最大行高为这个child的高度加上margin
maxLineHeight = child.getMeasuredHeight() + params.topMargin + params.bottomMargin;
// Log.v(TAG, "maxHeight:" + totalHeight + "---" + "maxWidth:" + maxWidth);
} else {
//不换行,累加宽度
lineWidth += deltaX;
//不换行,计算行最高
int deltaY = child.getMeasuredHeight() + params.topMargin + params.bottomMargin;
maxLineHeight = Math.max(maxLineHeight, deltaY);
}
if (i == count - 1) {
//前面没有加上下一行的搞,如果是最后一行,还要再叠加上最后一行的最高的值
totalHeight += maxLineHeight;
//计算最后一行和前面的最宽的一行比较
maxWidth = Math.max(lineWidth, oldWidth);
}
}
//加上当前容器的padding值
maxWidth += getPaddingLeft() + getPaddingRight();
totalHeight += getPaddingTop() + getPaddingBottom();
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : maxWidth,
heightMode == MeasureSpec.EXACTLY ? heightSize : totalHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
//pre为前面所有的child的相加后的位置
int preLeft = getPaddingLeft();
int preTop = getPaddingTop();
//记录每一行的最高值
int maxHeight = 0;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
//r-l为当前容器的宽度。如果子view的累积宽度大于容器宽度,就换行。
if (preLeft + params.leftMargin + child.getMeasuredWidth() + params.rightMargin + getPaddingRight() > (r - l)) {
//重置
preLeft = getPaddingLeft();
//要选择child的height最大的作为设置
preTop = preTop + maxHeight;
maxHeight = getChildAt(i).getMeasuredHeight() + params.topMargin + params.bottomMargin;
} else { //不换行,计算最大高度
maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + params.topMargin + params.bottomMargin);
}
//left坐标
int left = preLeft + params.leftMargin;
//top坐标
int top = preTop + params.topMargin;
int right = left + child.getMeasuredWidth();
int bottom = top + child.getMeasuredHeight();
//为子view布局
child.layout(left, top, right, bottom);
//计算布局结束后,preLeft的值
preLeft += params.leftMargin + child.getMeasuredWidth() + params.rightMargin;
}
}
}
xml
<com.lgd.factoryattendance.View.LineFeedRadioGroup android:id="@+id/rg_electric_code" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> </com.lgd.factoryattendance.View.LineFeedRadioGroup>
动态添加 RadioButton
List<String> list = new ArrayList<>(); // 动态 添加 RadioButton @SuppressLint({"ResourceType", "UseCompatLoadingForColorStateLists"}) public void addRadioButton(RadioGroup radioGroup){ list.clear(); list.add("B04011990126"); list.add("B04011990057"); list.add("B04011990087"); for (int i = 1; i < list.size(); i++) { RadioButton rb_craft = new RadioButton(HomePageActivity.this); // 设置 RadioButton 的大小和边距 ViewGroup.MarginLayoutParams marginLayoutParams = new ViewGroup.MarginLayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT ); marginLayoutParams.height = dpToPx(30); marginLayoutParams.setMarginEnd(dpToPx(10)); rb_craft.setLayoutParams(marginLayoutParams); rb_craft.setText( list.get(i)); rb_craft.setPadding(dpToPx(10),0,dpToPx(10),0); rb_craft.setButtonDrawable(null); rb_craft.setGravity(Gravity.CENTER); rb_craft.setBackgroundResource(R.drawable.selector_speed_borrow_select_day); rb_craft.setTextColor(getResources().getColorStateList(R.drawable.selector_select_day)); rb_craft.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 25); radioGroup.addView(rb_craft,marginLayoutParams); } } // dp 转化为 像素 public int dpToPx(int dp) { float density = getResources().getDisplayMetrics().density; return Math.round((float) dp * density); }
三.自定义 NumberPicker 实现 日期滚动
package com.lgd.factoryattendance.View;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.NumberPicker;
import com.lgd.factoryattendance.R;
import java.lang.reflect.Field;
/**
* @Author lgd
* @Date 2024/3/28 8:57
*自定义 NumberPicker 实现 日期滚动
*/
public class TextConfigNumberPicker extends NumberPicker {
public TextConfigNumberPicker(Context context) {
super(context);
}
public TextConfigNumberPicker(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TextConfigNumberPicker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public void addView(View child) {
this.addView(child, null);
}
@Override
public void addView(View child, ViewGroup.LayoutParams params) {
this.addView(child, 0, params);
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
super.addView(child, index, params);
updateView(child);
}
private void updateView(View view) {
if (view instanceof EditText) {
//设置通用(默认)文字的颜色和大小
((EditText) view).setTextColor(getResources().getColor(R.color.black));
((EditText) view).setTextSize(20);
}
try {
//设置分割线大小颜色
Field mSelectionDivider = this.getFile("mSelectionDivider");
mSelectionDivider.set(this, new ColorDrawable(getResources().getColor(R.color.lightskyblue)));
mSelectionDivider.set(this, 10);
} catch (Exception e) {
e.printStackTrace();
}
}
//反射获取控件 mSelectionDivider mInputText当前选择的view
public Field getFile(String fieldName) {
try {
//设置分割线的颜色值
Field pickerFields = NumberPicker.class.getDeclaredField(fieldName);
pickerFields.setAccessible(true);
return pickerFields;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//设置选中控件的style 注意一点要在setOnValueChangedListener或 setOnScrollChangeListener,setOnScrollListener中调
// 用picker.performClick();两种情况动画效果不同
//同时注意他的代用位置 要在当前控件初始化之后 否则获得不了
public void setMInputStyle(Float size) {
Field mInputText = this.getFile("mInputText");
try {
((EditText) mInputText.get(this)).setTextSize(size);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.lgd.factoryattendance.View.TextConfigNumberPicker
android:id="@+id/yearPicker"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:descendantFocusability="blocksDescendants"
/>
<com.lgd.factoryattendance.View.TextConfigNumberPicker
android:id="@+id/monthPicker"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:descendantFocusability="blocksDescendants"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal"
android:layout_marginTop="30dp"
android:layout_marginBottom="20dp">
<Button
android:id="@+id/Cancel_BT"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:text="取消"
android:background="@drawable/button"
android:textColor="@color/white"
android:textSize="20dp"
android:layout_marginRight="30dp"
android:layout_marginLeft="10dp"/>
<Button
android:id="@+id/Ok_BT"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:text="确定"
android:background="@drawable/button"
android:textColor="@color/white"
android:textSize="20dp"
android:layout_marginLeft="30dp"
android:layout_marginRight="10dp"/>
</LinearLayout>
</LinearLayout>
使用:
// 设置 年月 选择弹框控件 public void setData(){ // 加载自定义布局 View view = LayoutInflater.from(AdminActivity.this).inflate(R.layout.dialog_date_picker, null); final TextConfigNumberPicker yearPicker = view.findViewById(R.id.yearPicker); final TextConfigNumberPicker monthPicker = view.findViewById(R.id.monthPicker); final Button Ok_BT = view.findViewById(R.id.Ok_BT); final Button Cancel_BT = view.findViewById(R.id.Cancel_BT); // 设置年份范围 final int curYear = Calendar.getInstance().get(Calendar.YEAR); yearPicker.setMinValue(curYear); yearPicker.setMaxValue(curYear + 10); // 设置月份范围 String[] months = new String[]{"01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"}; monthPicker.setDisplayedValues(months); monthPicker.setMinValue(0); monthPicker.setMaxValue(11); yearPicker.setMInputStyle(16f); monthPicker.setMInputStyle(16f); // 设置滚动监听器 yearPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { picker.performClick(); //刷新选中状态 int year = picker.getValue(); int month = monthPicker.getValue() + 1; // 加1是因为数组下标从0开始 // Log.d("TAG", "Selected year: " + year + ", month: " + month); } }); monthPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { picker.performClick(); int year = yearPicker.getValue(); int month = picker.getValue() + 1; // 加1是因为数组下标从0开始 // Log.d("TAG", "Selected year: " + year + ", month: " + month); } }); // 构建AlertDialog AlertDialog.Builder builder = new AlertDialog.Builder(AdminActivity.this); builder.setTitle("请选择年月"); builder.setView(view); AlertDialog alertDialog = builder.create(); Ok_BT.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int year = yearPicker.getValue(); int month = monthPicker.getValue() + 1; // 加1是因为数组下标从0开始 StringBuilder sb = new StringBuilder(); sb.append(year).append("-").append(month); String str = timeConvert(sb.toString()); selectMonth_TV.setText(str); alertDialog.dismiss(); } }); Cancel_BT.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { alertDialog.dismiss(); } }); alertDialog.show(); } // 时间格式转换 public String timeConvert(String time){ SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-M"); SimpleDateFormat outputFormat = new SimpleDateFormat("yyyy-MM"); String time_str = null; try { Date date = inputFormat.parse(time); String formattedDate = outputFormat.format(date); time_str = formattedDate; } catch (ParseException e) { e.printStackTrace(); } return time_str; }
四. android悬浮按钮
package com.example.esp8266.activity.view;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.BounceInterpolator;
import android.view.animation.DecelerateInterpolator;
import androidx.annotation.NonNull;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
public class FloatBtn extends FloatingActionButton {
private static final String TAG = "FloatActionButton";
private int mLastX, mLastY;//按下时的X,Y坐标
private int mDownX, mDownY;//按下时的X,Y坐标 用来计算移动的距离
private int mScreenWidth, mScreenHeight;//ViewTree的宽和高
// 重写了所有的构造函数,因为不知道会用哪种
// 每个构造函数都进行了数据的初始化,即获取屏幕的高度和宽度
private int dx, dy, left, top , right, bottom;
public FloatBtn(Context context) {
super(context);
initData(context);
}
public FloatBtn(Context context, AttributeSet attrs) {
super(context, attrs);
initData(context);
}
public FloatBtn(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initData(context);
}
// 初始化数据:高度和宽度。看上去没啥问题,但是运行的时候浮动按钮的一部分会跑到虚拟导航栏的下面,也就是说获取的高度超过了显示的部分,但是宽度是正常的。
private void initData(Context context) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
mScreenHeight = (metrics.heightPixels)-150; // 减去导航栏 高度
mScreenWidth = metrics.widthPixels;
}
// 重写onTouchEvent方法,主要在这个方法里面来处理点击和拖动事件
@Override
public boolean onTouchEvent(@NonNull MotionEvent ev) {
int actionId = ev.getAction();
// Log.i(TAG, "onTouchEvent: " + actionId);
switch (actionId) {
// 无论是拖动还是点击 都需要先按下去,所以在这个case里记录按钮按下时的坐标
case MotionEvent.ACTION_DOWN:
// Log.i(TAG, "onTouchEvent: " + "DOWN");
mLastX = (int) ev.getRawX();// getRawX: 获取的距离屏幕边缘的距离,而不是组件的
mLastY = (int) ev.getRawY();
mDownX = mLastX;// 判断是点击事件还是拖动事件时需要用到
mDownY = mLastY;
break;
// 抬起事件:无论是拖动还是点击,完成之后都需要抬起按钮,在这个case里判断是拖动按钮还是点击按钮,主要是通过移动的距离来判断的,如果移动的距离超过5则认为是拖动,否则就是点击事件。如果是拖动事件则需要在此消费掉此事件,不会再执行onClick的方法。
case MotionEvent.ACTION_UP:
// Log.i(TAG, "onTouchEvent: " + "UP");
if (calculateDistance(mDownX, mDownY, (int) ev.getRawX(), (int) ev.getRawY()) <= 5) {
Log.i(TAG, "onTouchEvent: 点击事件");
} else {
Log.i(TAG, "onTouchEvent: 拖动事件");
// if (mLastX > mScreenWidth/2){
// //靠左吸附
// ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(this,"x",getX(),0);
// objectAnimator.setInterpolator(new BounceInterpolator()); //插值器 最好阶段回弹效果
// objectAnimator.setDuration(800);
// objectAnimator.start();
// }
// else {
// // 靠右吸附
// animate().setInterpolator(new DecelerateInterpolator()) //减速
// .setDuration(500)
// .xBy(mScreenWidth-getWidth()-getX())
// .start();
// }
// 消费掉此事件
return true;
}
break;
case MotionEvent.ACTION_MOVE: // 移动事件:根据移动之后的位置进行重绘
dx = (int) ev.getRawX() - mLastX;//x方向的偏移量
dy = (int) ev.getRawY() - mLastY;//y方向的偏移量
// getLeft(): Left position of this view relative to its parent.
// 计算组件此时的位置,距离父容器上下左右的距离=偏移量 + 原来的距离
left = getLeft() + dx;
top = getTop() + dy;
right = getRight() + dx;
bottom = getBottom() + dy;
// if (dy < 0) {
// Log.i(TAG, "onTouchEvent: 向上拖动");
// } else {
// Log.i(TAG, "onTouchEvent: 向下拖动");
// }
// if (dx < 0) {
// Log.i(TAG, "onTouchEvent: 向左拖动");
// } else {
// Log.i(TAG, "onTouchEvent: 向右拖动");
// }
mLastX = (int) ev.getRawX();
mLastY = (int) ev.getRawY();
if (top < 0) {
// 移出了上边界
top = 0;
bottom = getHeight();
}
if (left < 0) {
// 移出了左边界
left = 0;
right = getWidth();
}
if (bottom > mScreenHeight) {
// 移出了下边界
bottom = mScreenHeight;
top = bottom - getHeight();
}
if (right > mScreenWidth) {
// 移出了右边界
right = mScreenWidth;
left = right - getWidth();
}
layout(left, top, right, bottom);
break;
default:
break;
}
return super.onTouchEvent(ev);
}
// get the distance between (downX, downY) and (lastX, lastY)
private int calculateDistance(int downX, int downY, int lastX, int lastY) {
return (int) Math.sqrt(Math.pow(1.0f * (lastX - downX), 2.0) + Math.pow((lastY - downY) * 1.0f, 2.0));
}
}
使用:
<FrameLayout android:id="@+id/frameConfig" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:layout_marginTop="-5dp" tools:ignore="InefficientWeight"> <!--悬浮按钮--> <com.example.esp8266.activity.view.FloatBtn android:id="@+id/connect" android:layout_width="wrap_content" android:layout_height="wrap_content" android:clickable="true" android:focusable="true" android:layout_gravity="bottom|end" android:layout_marginBottom="25dp" android:layout_marginEnd="20dp" tools:ignore="InvalidId" android:src="@drawable/link_ico" android:scaleType="center" app:fabSize="normal" app:borderWidth="0dp" android:tooltipText="@string/linkBoard" tools:targetApi="O" /> </FrameLayout>
五. 自定义仿iOS 音量,亮度调节
自定义音量 View
package com.lgd.factoryattendance.View;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
import com.lgd.factoryattendance.R;
/**
* @Author lgd
* @Date 2024/8/29 13:35
*/
public class IosColumnAudioView extends View {
private Context mContext;
//日志TAG
private static final String TAG = IosColumnAudioView.class.getName();
//系统声音广播名
private static final String VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION";
private static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE";
//系统声音广播接收器
private MyVolumeReceiver mVolumeReceiver = null;
//标记-是否是当前自己在调整音量大小
private boolean isMeAdjustVolume = true;
//当前UI高度与view高度的比例
private double mCurrentDrawLoudRate = 0;
//当前真实音量与总音量大小比例
private double mCurrentRealLoudRate = 0;
//系统最大声音index
private int mMaxLoud=0;
//记录按压时手指相对于组件view的高度
private float mDownY;
//手指移动的距离,视为音量调整
private float mMoveDistance;
//系统audio管理
private AudioManager mAudioManager;
//当前音量文字数字
private String mTextLoud ="";
//画笔
private Paint mPaint;
//位置
private RectF mRectF;
//当前Canvas LayerId
private int layerId = 0;
//音量图标圆弧位置
private RectF mRectVolumeArc =new RectF();
//音量图标静音位置
private Rect mRectVolumeDrawable=new Rect();
//音量图标margin
private int mRectVolumeDrawableMargin=10;
//音量图标粗细
private final static int mRectVolumeDrawableWidth=4;
//音量图标开始角度
private final static int mRectVolumeDrawableStartAngle=315;
//音量图标终止角度
private final static int mRectVolumeDrawableEndAngle=90;
/**
* 设置声音流类型-默认音乐-iosColumnAudioView_setAudioStreamType
*/
private int mAudioManagerStreamType = AudioManager.STREAM_MUSIC;
/**
* 设置圆弧度数-xml-iosColumnAudioView_setRadiusXY
*/
private float mRXY=40;
/**
* 设置当前音量颜色-xml-iosColumnAudioView_setColorLoud
*/
private int mColorLoud = Color.parseColor("#ECECEC");
/**
* 设置组件背景颜色-xml-iosColumnAudioView_setColorBackground
*/
private int mColorBackground = Color.parseColor("#898989");
/**
* 设置是否画音量文字-iosColumnAudioView_setIsDrawTextVolume
* @param context
*/
private boolean mIsDrawTextVolume = true;
/**
* 设置文字大小-xml-iosColumnAudioView_setTextSize
*/
private float mTextSize = 15;
/**
* 设置文字颜色-xml-iosColumnAudioView_setTextColor
*/
private int mTextColor = Color.BLACK;
/**
* 设置文字高度-xml-iosColumnAudioView_setTextHeight
* @param context
*/
private int mTextHeight = -1;
/**
* 设置是否画音量图标-iosColumnAudioView_setIsDrawDrawableVolume
* @param context
*/
private boolean mIsDrawDrawableVolume = true;
/**
* 设置音量圆弧颜色-xml-iosColumnAudioView_setColorVolume
*/
private int mColorVolume = Color.DKGRAY;
/**
* 设置音量静音图标drawable-xml-iosColumnAudioView_setColorVolumeDrawable
*/
private Drawable mColorDrawable = null;
//固定组件高度长度,这里不做适配,可自行修改
private int mViewHeight = 150, mViewWeight = 50;
public IosColumnAudioView(Context context) {
super(context);
initial(context);
}
public IosColumnAudioView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mTextSize = sp2px(context,mTextSize);
TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.IosColumnAudioView);
mColorBackground = typedArray.getColor(R.styleable.IosColumnAudioView_iosColumnAudioView_setColorBackground,mColorBackground);
mColorLoud = typedArray.getColor(R.styleable.IosColumnAudioView_iosColumnAudioView_setColorLoud,mColorLoud);
// mAudioManagerStreamType =typedArray.getInteger(R.styleable.IosColumnAudioView_iosColumnAudioView_setAudioStreamType, mAudioManagerStreamType);
mRXY = typedArray.getDimension(R.styleable.IosColumnAudioView_iosColumnAudioView_setRadiusXY, mRXY);
mTextSize = typedArray.getDimension(R.styleable.IosColumnAudioView_iosColumnAudioView_setTextSize,mTextSize);
mTextColor = typedArray.getColor(R.styleable.IosColumnAudioView_iosColumnAudioView_setTextColor,mTextColor);
mTextHeight = typedArray.getInt(R.styleable.IosColumnAudioView_iosColumnAudioView_setTextHeight,mTextHeight);
mIsDrawTextVolume = typedArray.getBoolean(R.styleable.IosColumnAudioView_iosColumnAudioView_setIsDrawTextVolume,mIsDrawTextVolume);
mIsDrawDrawableVolume = typedArray.getBoolean(R.styleable.IosColumnAudioView_iosColumnAudioView_setIsDrawDrawableVolume,mIsDrawDrawableVolume);
mColorVolume = typedArray.getColor(R.styleable.IosColumnAudioView_iosColumnAudioView_setVolumeColor, mColorVolume);
mColorDrawable = typedArray.getDrawable(R.styleable.IosColumnAudioView_iosColumnAudioView_setVolumeDrawable);
typedArray.recycle();
initial(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mRectVolumeDrawableMargin = MeasureSpec.getSize(widthMeasureSpec)/10;
//固定组件高度长度,这里不做适配,可自行修改
setMeasuredDimension(dp2px(mContext,mViewWeight),dp2px(mContext,mViewHeight));
}
private void initial(Context context){
mContext=context;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mMaxLoud = mAudioManager.getStreamMaxVolume(mAudioManagerStreamType);
mCurrentDrawLoudRate = getCalculateLoudRate();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mRectF = new RectF();
setWillNotDraw(false);
setBackgroundColor(Color.TRANSPARENT);
mPaint.setTextSize(mTextSize);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mDownY=event.getY();
isMeAdjustVolume=true;
break;
case MotionEvent.ACTION_MOVE:
mMoveDistance = mDownY - event.getY();
calculateLoudRate();
mDownY=event.getY();
break;
case MotionEvent.ACTION_UP:
isMeAdjustVolume=false;
break;
default:
break;
}
refreshAll();
return true;
}
/**
* 更新所有内容-ui-音乐大小
*/
private void refreshAll(){
refreshStreamVolume(((int)(mCurrentDrawLoudRate * mMaxLoud))<= mMaxLoud? (int)(mCurrentDrawLoudRate * mMaxLoud) : mMaxLoud);
refreshUI();
}
/**
* 设置音量大小
* @param currentVolume
*/
public void refreshStreamVolume(int currentVolume){
mAudioManager.setStreamVolume(mAudioManagerStreamType,currentVolume, 0);
}
/**
* 刷新UI
*/
public void refreshUI(){
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
layerId = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);
onDrawBackground(canvas); //画背景
onDrawLoud(canvas); //画当前音量前景表示当前多大音量
onDrawText(canvas); //画文字
onDrawVolumeDrawable(canvas);//画底部音量图标
canvas.restoreToCount(layerId);
}
/**
* 计算手指移动后音量UI占比大小,视其为音量大小
*/
private void calculateLoudRate(){
mCurrentDrawLoudRate = ( getHeight() * mCurrentDrawLoudRate + mMoveDistance) / getHeight();
if(mCurrentDrawLoudRate >=1){
mCurrentDrawLoudRate =1;
}
if(mCurrentDrawLoudRate <=0){
mCurrentDrawLoudRate =0;
}
}
/**
* 画圆弧背景
* @param canvas
*/
private void onDrawBackground(Canvas canvas){
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mColorBackground );
mRectF.left=0;
mRectF.top=0;
mRectF.right=canvas.getWidth();
mRectF.bottom=canvas.getHeight();
canvas.drawRoundRect(mRectF,mRXY,mRXY,mPaint);
}
/**
* 画音量背景-方形-随手势上下滑动而变化用来显示音量大小
* @param canvas
*/
private void onDrawLoud(Canvas canvas){
mPaint.setStyle(Paint.Style.FILL);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
mPaint.setColor(mColorLoud);
mRectF.left=0;
mRectF.top=(canvas.getHeight()-(int)(canvas.getHeight() * mCurrentDrawLoudRate));
mRectF.right=canvas.getWidth();
mRectF.bottom=canvas.getHeight();
canvas.drawRect(mRectF,mPaint);
mPaint.setXfermode(null);
}
/**
* 画文字-展示当前语音大小
* @param canvas
*/
private void onDrawText(Canvas canvas){
if(mIsDrawTextVolume) { //如果开启了则开始绘制
mPaint.setStyle(Paint.Style.FILL);
mTextLoud = "" + (int) (mCurrentDrawLoudRate * 100);
mPaint.setColor(mTextColor);
canvas.drawText(mTextLoud, (canvas.getWidth() / 2 - mPaint.measureText(mTextLoud) / 2), mTextHeight >= 0 ? mTextHeight : getHeight() / 6, mPaint);
}
}
/**
* 画音量图标
*/
private void onDrawVolumeDrawable(Canvas canvas){
if(mIsDrawDrawableVolume){ //如果开启了则开始绘制
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mRectVolumeDrawableWidth);
mPaint.setColor(mColorVolume);
if (getCalculateLoudRate()>0){ //如果当前实际系统音量>0,则绘圆弧,否则绘制静音图片
onDrawVolumeDrawableArc(canvas); //画音量圆弧
}else if (getCalculateLoudRate() == 0 && mColorDrawable != null){
onDrawVolumeMutedDrawable(canvas);
}
}
}
/**
* 画音量图片-静音drawable
*/
private void onDrawVolumeMutedDrawable(Canvas canvas){
mRectVolumeDrawable.left=mRectVolumeDrawableMargin * 3;
mRectVolumeDrawable.right=canvas.getWidth()-mRectVolumeDrawableMargin * 3;
mRectVolumeDrawable.bottom=(int)(canvas.getHeight()*0.9)-mRectVolumeDrawableMargin * 3;
mRectVolumeDrawable.top= (int)(mRectVolumeDrawable.bottom-canvas.getWidth()+mRectVolumeDrawableMargin *2 *3);
mColorDrawable.setBounds(mRectVolumeDrawable);
mColorDrawable.draw(canvas);
}
/**
* 画音量图标-圆弧-计算多少个,这里是展示4个
*/
private void onDrawVolumeDrawableArc(Canvas canvas){
for(int i = 0; i<=(int)(mCurrentDrawLoudRate /0.33); i++){
onDrawVolumeDrawableArc(canvas,(int)(mCurrentDrawLoudRate /0.33)-i); //画圆弧
}
}
/**
* 画音量图标-圆弧
*/
private void onDrawVolumeDrawableArc(Canvas canvas, int index){
index=4-index;
mRectVolumeArc.left=mRectVolumeDrawableMargin * index;
mRectVolumeArc.right=canvas.getWidth()-mRectVolumeDrawableMargin * index;
mRectVolumeArc.bottom=(int)(canvas.getHeight()*0.9)-mRectVolumeDrawableMargin * index;
mRectVolumeArc.top= mRectVolumeArc.bottom-canvas.getWidth()+mRectVolumeDrawableMargin *2 *index;
//画板偏移
canvas.translate(-mRectVolumeDrawableMargin,0);
//开始角度向 360度收缩,结束角度向0度收缩,这样就可以造成弧度随着音量扩大而扩大,缩小而缩小的感觉
canvas.drawArc(
mRectVolumeArc,
(float) (mRectVolumeDrawableStartAngle + ( (360 - mRectVolumeDrawableStartAngle) * (1- mCurrentDrawLoudRate))),
(float) (mRectVolumeDrawableEndAngle - ( (mRectVolumeDrawableEndAngle-0) * (1- mCurrentDrawLoudRate))),
false,
mPaint);
}
/**
* 将sp值转换为px值,保证文字大小不变
*/
public int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
/**
* 监听view视图在window里时,监听系统声音并注册广播
*/
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
myRegisterReceiver(mContext);
}
/**
* 监听view视图从window里抽离的时,取消广播的注册
*/
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
unRegisterReceiver();//取消注册系统声音监听
}
/**
* 注册音量广播-监听音量改变事件
*/
private void myRegisterReceiver(Context context) {
if(null != context) {
mVolumeReceiver = new MyVolumeReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(VOLUME_CHANGED_ACTION);
context.registerReceiver(mVolumeReceiver, intentFilter);
}
}
/**
* 取消广播
*/
private void unRegisterReceiver(){
if(mVolumeReceiver!=null) {
mContext.unregisterReceiver(mVolumeReceiver);
}
}
/**
* 继承广播接受者
*/
private class MyVolumeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(VOLUME_CHANGED_ACTION)){
if(intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1) == mAudioManagerStreamType){
if(!isMeAdjustVolume) {
mCurrentDrawLoudRate = getCalculateLoudRate();
refreshUI();
}
}
}
}
}
/**
* 计算音量比例
*/
private double getCalculateLoudRate(){
return (double) mAudioManager.getStreamVolume(mAudioManagerStreamType)/ mMaxLoud;
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dp2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
自定义 亮度 View
package com.lgd.factoryattendance.View;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
import com.lgd.factoryattendance.R;
/**
* @Author lgd
* @Date 2024/8/29 14:10
*/
public class IosColumnBrightnessView extends View {
private Context mContext;
//日志TAG
private static final String TAG = IosColumnBrightnessView.class.getName();
//系统亮度监听
private BrightnessObserver mBrightnessObserver = null;
//当前圆心最大半径
private float mCircleMaxRadius = 0;
//当前圆心最小半径
private float mCircleMinRadius = 0;
//当前圆心边长
private float mCircleMaxWidth = 0;
//当前UI高度与view高度的比例
private double mCurrentDrawBrightnessRate = 0;
//系统最大亮度index-默认255
private final int mMaxBrightness = 255;
//记录按压时手指相对于组件view的高度
private float mDownY;
//手指移动的距离,视为亮度调整
private float mMoveDistance;
//系统audio管理
private AudioManager mAudioManager;
//当前亮度文字数字
private String mTextLoud ="";
//画笔
private Paint mPaint;
//位置
private RectF mRectF;
//当前Canvas LayerId
private int layerId = 0;
//亮度图标margin
private int mRectBrightnessDrawableMargin =10;
//亮度图标粗细
private final static int mRectBrightnessDrawableWidth =4;
/**
* 设置圆弧度数-xml-iosColumnAudioView_setRadiusXY
*/
private float mRXY=40;
/**
* 设置当前亮度颜色-xml-iosColumnAudioView_setColorLoud
*/
private int mColorLoud = Color.parseColor("#ECECEC");
/**
* 设置组件背景颜色-xml-iosColumnAudioView_setColorBackground
*/
private int mColorBackground = Color.parseColor("#898989");
/**
* 设置是否画亮度文字-iosColumnAudioView_setIsDrawTextVolume
* @param context
*/
private boolean mIsDrawTextVolume = true;
/**
* 设置文字大小-xml-iosColumnAudioView_setTextSize
*/
private float mTextSize = 15;
/**
* 设置文字颜色-xml-iosColumnAudioView_setTextColor
*/
private int mTextColor = Color.BLACK;
/**
* 设置文字高度-xml-iosColumnAudioView_setTextHeight
* @param context
*/
private int mTextHeight = -1;
/**
* 设置是否画亮度图标-iosColumnAudioView_setIsDrawDrawableVolume
* @param context
*/
private boolean mIsDrawDrawableVolume = true;
/**
* 设置亮度圆弧颜色-xml-iosColumnAudioView_setColorVolume
*/
private int mColorVolume = Color.DKGRAY;
//固定组件高度长度,这里不做适配,可自行修改
private int mViewHeight = 150, mViewWeight=50;
public IosColumnBrightnessView(Context context) {
super(context);
initial(context);
}
public IosColumnBrightnessView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mTextSize = sp2px(context,mTextSize);
TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.IosColumnBrightnessView);
mColorBackground = typedArray.getColor(R.styleable.IosColumnBrightnessView_iosColumnBrightnessView_setColorBackground,mColorBackground);
mColorLoud = typedArray.getColor(R.styleable.IosColumnBrightnessView_iosColumnBrightnessView_setColorBright,mColorLoud);
mRXY = typedArray.getDimension(R.styleable.IosColumnBrightnessView_iosColumnBrightnessView_setRadiusXY, mRXY);
mTextSize = typedArray.getDimension(R.styleable.IosColumnBrightnessView_iosColumnBrightnessView_setTextSize,mTextSize);
mTextColor = typedArray.getColor(R.styleable.IosColumnBrightnessView_iosColumnBrightnessView_setTextColor,mTextColor);
mTextHeight = typedArray.getInt(R.styleable.IosColumnBrightnessView_iosColumnBrightnessView_setTextHeight,mTextHeight);
mIsDrawTextVolume = typedArray.getBoolean(R.styleable.IosColumnBrightnessView_iosColumnBrightnessView_setIsDrawTextBright,mIsDrawTextVolume);
mIsDrawDrawableVolume = typedArray.getBoolean(R.styleable.IosColumnBrightnessView_iosColumnBrightnessView_setIsDrawDrawableBright,mIsDrawDrawableVolume);
mColorVolume = typedArray.getColor(R.styleable.IosColumnBrightnessView_iosColumnBrightnessView_setBrightnessColor, mColorVolume);
typedArray.recycle();
initial(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mRectBrightnessDrawableMargin = MeasureSpec.getSize(widthMeasureSpec)/10;
//固定组件高度长度,这里不做适配,可自行修改
setMeasuredDimension(dp2px(mContext,mViewWeight),dp2px(mContext,mViewHeight));
}
private void initial(Context context){
mContext=context;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mCurrentDrawBrightnessRate = getCalculateBrightnessRate();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mRectF = new RectF();
setWillNotDraw(false);
setBackgroundColor(Color.TRANSPARENT);
mPaint.setTextSize(mTextSize);
getPermission();
// stopAutoBrightness(mContext);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
mDownY=event.getY();
break;
case MotionEvent.ACTION_MOVE:
mMoveDistance = mDownY - event.getY();
calculateLoudRate();
mDownY=event.getY();
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
refreshAll();
return true;
}
/**
* 更新所有内容-ui-系统亮度
*/
private void refreshAll(){
setSystemBrightness((int)(mCurrentDrawBrightnessRate * mMaxBrightness));
refreshUI();
}
/**
* 刷新UI
*/
public void refreshUI(){
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
layerId = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);
onDrawBackground(canvas); //画背景
onDrawFront(canvas); //画当前亮度前景表示当前多大亮度
onDrawText(canvas); //画文字
onDrawSunCircle(canvas);//画底部图标圆心
canvas.restoreToCount(layerId);
}
/**
* 计算手指移动后亮度UI占比大小,视其为亮度大小
*/
private void calculateLoudRate(){
mCurrentDrawBrightnessRate = ( getHeight() * mCurrentDrawBrightnessRate + mMoveDistance) / getHeight();
if(mCurrentDrawBrightnessRate >=1){
mCurrentDrawBrightnessRate =1;
}
if(mCurrentDrawBrightnessRate <=0){
mCurrentDrawBrightnessRate =0;
}
}
/**
* 画圆弧背景
* @param canvas
*/
private void onDrawBackground(Canvas canvas){
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mColorBackground );
mRectF.left=0;
mRectF.top=0;
mRectF.right=canvas.getWidth();
mRectF.bottom=canvas.getHeight();
canvas.drawRoundRect(mRectF,mRXY,mRXY,mPaint);
}
/**
* 画亮度背景-方形-随手势上下滑动而变化用来显示亮度大小
* @param canvas
*/
private void onDrawFront(Canvas canvas){
mPaint.setStyle(Paint.Style.FILL);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
mPaint.setColor(mColorLoud);
mRectF.left=0;
mRectF.top=(canvas.getHeight()-(int)(canvas.getHeight() * mCurrentDrawBrightnessRate));
mRectF.right=canvas.getWidth();
mRectF.bottom=canvas.getHeight();
canvas.drawRect(mRectF,mPaint);
mPaint.setXfermode(null);
}
/**
* 画文字-展示当前语音大小
* @param canvas
*/
private void onDrawText(Canvas canvas){
if(mIsDrawTextVolume) { //如果开启了则开始绘制
mPaint.setStyle(Paint.Style.FILL);
mTextLoud = "" + (int) (mCurrentDrawBrightnessRate * 100);
mPaint.setColor(mTextColor);
canvas.drawText(mTextLoud, (canvas.getWidth() / 2 - mPaint.measureText(mTextLoud) / 2), mTextHeight >= 0 ? mTextHeight : getHeight() / 6, mPaint);
}
}
/**
* 画亮度图标-太阳圆心
*/
private void onDrawSunCircle(Canvas canvas){
if(mIsDrawDrawableVolume){ //如果开启了则开始绘制
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(mRectBrightnessDrawableWidth);
mPaint.setColor(mColorVolume);
mCircleMaxRadius = (float) (Math.sqrt(canvas.getWidth()) * 1.5);
mCircleMinRadius = (float) (Math.sqrt(canvas.getWidth()) * 1);
mCircleMaxWidth = (float) mCurrentDrawBrightnessRate * (mCircleMaxRadius-mCircleMinRadius)+mCircleMinRadius;
canvas.drawCircle(canvas.getWidth()/2,(float) (canvas.getHeight()*0.8- mRectBrightnessDrawableMargin), mCircleMaxWidth,mPaint);
onDrawSunRays(canvas,canvas.getWidth()/2,(float) (canvas.getHeight()*0.8- mRectBrightnessDrawableMargin));
}
}
/**
* 画亮度图标-太阳光芒
*/
private void onDrawSunRays(Canvas canvas,float cx,float cy){
mPaint.setStrokeCap(Paint.Cap.ROUND); // 定义线段断电形状为圆头
//绘制时刻度
canvas.translate(cx,cy);
for (int i = 0; i < 10; i++) {
canvas.drawLine(mCircleMaxWidth, mCircleMaxWidth, (float)(mCircleMaxWidth+5* mCurrentDrawBrightnessRate),(float)( mCircleMaxWidth+5* mCurrentDrawBrightnessRate), mPaint);
canvas.rotate(36);
}
}
/**
* 监听view视图在window里时,监听系统亮度
*/
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if(mContext != null) {
mBrightnessObserver = new BrightnessObserver(mContext, new Handler(Looper.getMainLooper()));
}
}
/**
* 监听view视图从window里抽离的时,取消广播的注册
*/
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if(null != mBrightnessObserver) {
mBrightnessObserver.unregister();
}
}
/**
* 监听亮度改变
*/
private class BrightnessObserver extends ContentObserver {
private Context mContext;
public BrightnessObserver(Context context, Handler handler) {
super(handler);
this.mContext =context;
register();
}
//注册监听
public void register(){
Uri brightnessUri = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
mContext.getContentResolver().registerContentObserver(brightnessUri, true,this);
}
//取消注册监听
public void unregister(){
mContext.getContentResolver().unregisterContentObserver(this);
}
@Override
public void onChange(boolean selfChange) {
//selfChange 一直是false,无法区分是自己手动改变还是通过系统设置调节,有bug。
super.onChange(selfChange);
}
}
/**
* 改变当前系统亮度
* @return
*/
public void setSystemBrightness(int brightness) {
if(null != mContext) {
if(brightness>=mMaxBrightness){
brightness=mMaxBrightness;
}
if(brightness<=0){
brightness=0;
}
Settings.System.putInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS,brightness);
}
}
/**
* 获取当前系统亮度
* @return
*/
public int getSystemBrightness(){
if(null != mContext) {
try {
return Settings.System.getInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS);
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
}
}
return 0;
}
/**
* 计算亮度比例
*/
private double getCalculateBrightnessRate(){
return (double) getSystemBrightness()/ mMaxBrightness;
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dp2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 将sp值转换为px值,保证文字大小不变
*/
public int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
/**
*
*/
private void getPermission(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.System.canWrite(mContext)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
intent.setData(Uri.parse("package:" + mContext.getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
} else {
// 申请权限后做的操作
}
}
}
/**
* 停止自动亮度调节
*/
private void stopAutoBrightness(Context context) {
Settings.System.putInt(context.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
}
}
权限:<uses-permission android:name="android.permission.WRITE_SETTINGS" tools:ignore="ProtectedPermissions" />
attrs.xml:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="IosColumnAudioView"> <attr name="iosColumnAudioView_setColorBackground" format="color"></attr> <attr name="iosColumnAudioView_setColorLoud" format="color"></attr> <attr name="iosColumnAudioView_setRadiusXY" format="dimension"></attr> <attr name="iosColumnAudioView_setTextSize" format="dimension"></attr> <attr name="iosColumnAudioView_setTextColor" format="color"></attr> <attr name="iosColumnAudioView_setTextHeight" format="integer|dimension"></attr> <attr name="iosColumnAudioView_setIsDrawTextVolume" format="boolean"></attr> <attr name="iosColumnAudioView_setIsDrawDrawableVolume" format="boolean"></attr> <attr name="iosColumnAudioView_setVolumeColor" format="color"></attr> <attr name="iosColumnAudioView_setVolumeDrawable" format="reference"></attr> </declare-styleable> <declare-styleable name="IosColumnBrightnessView"> <attr name="iosColumnBrightnessView_setColorBackground" format="color"></attr> <attr name="iosColumnBrightnessView_setColorBright" format="color"></attr> <attr name="iosColumnBrightnessView_setRadiusXY" format="dimension"></attr> <attr name="iosColumnBrightnessView_setTextSize" format="dimension"></attr> <attr name="iosColumnBrightnessView_setTextColor" format="color"></attr> <attr name="iosColumnBrightnessView_setTextHeight" format="integer|dimension"></attr> <attr name="iosColumnBrightnessView_setIsDrawTextBright" format="boolean"></attr> <attr name="iosColumnBrightnessView_setIsDrawDrawableBright" format="boolean"></attr> <attr name="iosColumnBrightnessView_setBrightnessColor" format="color"></attr> </declare-styleable> </resources>
使用:
<LinearLayout app:layout_constraintTop_toTopOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintBottom_toBottomOf="parent" android:orientation="horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content"> <com.lgd.factoryattendance.View.IosColumnAudioView android:id="@+id/ios_audio" android:layout_margin="20dp" app:iosColumnAudioView_setRadiusXY="20dp" app:iosColumnAudioView_setVolumeDrawable="@drawable/tishi" android:layout_width="60dp" android:layout_height="200dp"> </com.lgd.factoryattendance.View.IosColumnAudioView> <com.lgd.factoryattendance.View.IosColumnBrightnessView android:id="@+id/ios_brightness" android:layout_margin="20dp" app:iosColumnBrightnessView_setRadiusXY="20dp" android:layout_width="60dp" android:layout_height="200dp"> </com.lgd.factoryattendance.View.IosColumnBrightnessView> </LinearLayout>
六.自定义组合控件
自定义界面:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="50dp"> <ImageView android:id="@+id/item_icon" android:layout_width="30dp" android:layout_height="30dp" android:layout_margin="10dp" android:src="@drawable/tishi" android:layout_centerVertical="true"/> <TextView android:id="@+id/item_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toRightOf="@+id/item_icon" android:text="texttttt" android:textColor="#000000" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="11111" android:layout_centerInParent="true"/> <TextView android:id="@+id/item_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Crystal" android:layout_centerVertical="true" android:textColor="#B8860B" android:visibility="visible" android:layout_toLeftOf="@+id/item_arrow"/> <ImageView android:id="@+id/item_arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:src="@drawable/down" android:layout_alignParentRight="true" android:layout_marginRight="10dp" /> <View android:id="@+id/item_line" android:layout_width="match_parent" android:layout_height="0.5dp" android:layout_alignParentBottom="true" android:background="#D3D3D3" android:visibility="visible" /> </RelativeLayout>
自定义组合控件:
public class ItemView extends LinearLayout { private Boolean isBottom; private Boolean isItemText; private Boolean isArrow; private ImageView itemIcon; private ImageView rightArrow; private TextView leftTitle; private TextView itemText; private View itemLine; public ItemView(Context context, AttributeSet attrs) { super(context, attrs); LayoutInflater.from(getContext()).inflate(R.layout.item_view, this); TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ItemView);//解析布局 isBottom = ta.getBoolean(R.styleable.ItemView_show_bottom_line, true); //底部分割线是否显示 isItemText = ta.getBoolean(R.styleable.ItemView_show_right_text, true);//右边描述文字是否显示 isArrow = ta.getBoolean(R.styleable.ItemView_show_right_arrow, true); //右边箭头是否显示 itemIcon = findViewById(R.id.item_icon); //左边图标 rightArrow = findViewById(R.id.item_arrow);//右边箭头 leftTitle = findViewById(R.id.item_title); //左边标题文字 itemText = findViewById(R.id.item_text); //右边描述文字或数值 itemLine = findViewById(R.id.item_line); //底部分割线 itemIcon.setImageDrawable(ta.getDrawable(R.styleable.ItemView_left_icon)); //图标赋值 // leftTitle.setText(ta.getText(R.styleable.ItemView_left_title)); // itemText.setText(ta.getText(R.styleable.ItemView_right_text)); setLeftTitle(ta.getText(R.styleable.ItemView_left_title).toString()); setItemText(ta.getText(R.styleable.ItemView_right_text).toString()); itemText.setTextColor(ta.getColor(R.styleable.ItemView_right_text_color, Color.BLACK)); itemText.setVisibility(isItemText ? VISIBLE : INVISIBLE); itemLine.setVisibility(isBottom ? VISIBLE : INVISIBLE); rightArrow.setVisibility(isArrow ? VISIBLE : GONE); //回收属性对象 ta.recycle(); } public void setLeftTitle(String text) { leftTitle.setText(text); } public void setItemText(String text) { itemText.setText(text); } }
attrs.xml:
<declare-styleable name="ItemView"> <attr name="left_icon" format="integer" /><!--左侧图标--> <attr name="left_title" format="string" /><!--左侧标题文字--> <attr name="right_text" format="string" /><!--右侧描述文字--> <attr name="right_text_color" format="color" /><!--右侧描述文字颜色--> <attr name="show_right_text" format="boolean" /><!--是否显示右侧描述文字--> <attr name="show_right_arrow" format="boolean" /> <!--是否显示右侧箭头--> <attr name="show_bottom_line" format="boolean" /> <!--是否显示下划线--> </declare-styleable>
七.自定义流式布局
方法一:
public class FlowLayout extends ViewGroup {
private int mHorizontalSpacing = dp2px(16); //每个item横向间距
private int mVerticalSpacing = dp2px(8); //每个item横向间距
private List<List<View>> allLines = new ArrayList<>(); // 记录所有的行,一行一行的存储,用于layout
List<Integer> lineHeights = new ArrayList<>(); // 记录每一行的行高,用于layout
public FlowLayout(Context context) {
super(context);
}
public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void clearMeasureParams() {
allLines.clear();
lineHeights.clear();
}
/*
* 1、度量子view
* 2、获取子view的宽、高、换行等
* 3、向父类索要宽高,判断是哪种MeasureSpecMode,根据不同的mode给出不同的区域
* 4、保存记录
* */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
clearMeasureParams();
List<View> lineViews = new ArrayList<>(); //保存一行中的所有的view
int lineWidthUsed = 0; //记录这行已经使用了多宽的size
int lineHeight = 0; // 一行的行高
int selfWidth = MeasureSpec.getSize(widthMeasureSpec); //父view给我的宽度
int selfHeight = MeasureSpec.getSize(heightMeasureSpec); //父view给我的高度
int flowLayoutNeedWidth = 0; // measure过程中,FlowLayout要求的父ViewGroup的宽
int flowLayoutNeedHeight = 0; // measure过程中,FlowLayout要求的父ViewGroup的高
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
// 获取子view数
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
// 获取子view
View childView = getChildAt(i);
// 获取子view的layoutParams,通过layoutParams可得到子view的宽高具体值或者MATCH_PARENT还是WRAP_CONTENT
LayoutParams childLP = childView.getLayoutParams();
if (childView.getVisibility() != View.GONE) {
//将layoutParams转变成为 measureSpec 即设置子view的measureSpec
/*
* widthMeasureSpec表示父view给予FlowLayout的宽度
* paddingLeft + paddingRight表示父view所设置的左右padding值
* childLP.width 表示 子view的宽度
* */
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, childLP.width);
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, childLP.height);
// 通过子view的measureSpec度量子view
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//获取子view的度量宽高
int childMeasureWidth = childView.getMeasuredWidth();
int childMeasureHeight = childView.getMeasuredHeight();
//换行
if(lineWidthUsed + mHorizontalSpacing + childMeasureWidth > selfWidth ){
//一旦换行,我们就可以判断当前行需要的宽和高,所以此时要记录下来
allLines.add(lineViews);
lineHeights.add(lineHeight);
//判断flowLayout到底需要多宽、多高
flowLayoutNeedWidth = Math.max(flowLayoutNeedWidth, lineWidthUsed);
flowLayoutNeedHeight = flowLayoutNeedHeight + lineHeight + mVerticalSpacing;
// 换行后初始化
// 此处不能用clear,用clear则allLines里面的item所指向的就是同一个内存地址了
lineViews = new ArrayList<>();
lineWidthUsed = 0;
lineHeight = 0;
}
//每行的设置
lineViews.add(childView);
lineWidthUsed = lineWidthUsed + mHorizontalSpacing + childMeasureWidth;
lineHeight = Math.max(lineHeight, childMeasureHeight);
//最后一行数据(因为最后一行的时候到不了换行的那句代码,所以不会显示,因此要单独判断)
if(i == childCount -1){
allLines.add(lineViews);
lineHeights.add(lineHeight);
//判断flowLayout到底需要多宽、多高
flowLayoutNeedWidth = Math.max(flowLayoutNeedWidth, lineWidthUsed);
flowLayoutNeedHeight = flowLayoutNeedHeight + lineHeight + mVerticalSpacing;
}
}
}
//根据子View的度量结果,来重新度量自己ViewGroup
// 作为一个ViewGroup,它自己也是一个View,它的大小也需要根据它的父view给它提供的宽高来度量
//首先获取到父view的MeasureSpec的mode
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// 如果父view的MeasureSpec的mode是EXACTLY表示宽度是确切的,则selfWidth为最终宽度,否则为
int flowLayoutWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : flowLayoutNeedWidth;
int flowLayoutHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : flowLayoutNeedHeight;
// 保存记录
setMeasuredDimension(flowLayoutWidth, flowLayoutHeight);
}
// 布局(每一行每一行的布局)
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 获取行数 最大显示3行
int lineCount = Math.min(allLines.size(), 3);
// 获取flowLayout所设置的pandding值,布局从左上角开始
int curL = getPaddingLeft();
int curT = getPaddingTop();
for (int i = 0; i < lineCount; i++) {
// 获取到每一行的所有view
List<View> lineViews = allLines.get(i);
for (int j = 0; j < lineViews.size(); j++){
//获取单个view
View view = lineViews.get(j);
//设置view的视图坐标系,
int left = curL;
int top = curT;
int right = left + view.getMeasuredWidth();
int bottom = top + view.getMeasuredHeight();
// view添加到布局
view.layout(left,top,right,bottom);
// 计算下一个view的宽度的开始位置
curL = right + mHorizontalSpacing;
}
// 计算下一行view的高度的开始位置
curT = curT + lineHeights.get(i) + mVerticalSpacing;
// 宽度位置初始化
curL = getPaddingLeft();
}
}
public List<List<View>> getAllLines() {
return allLines;
}
public static int dp2px(int dp) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());
}
}
方法二:
public class FlowLayout extends ViewGroup {
private final String TAG = "FlowLayout";
private float mRowSpacing = 0.0f;// 行间距
private float mColumnSpacing = 0.0f;// 列间距
public FlowLayout(Context context) {
this(context, null);
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
mRowSpacing = typedArray.getDimensionPixelSize(R.styleable.FlowLayout_rowSpacing, 0);
mColumnSpacing = typedArray.getDimensionPixelSize(R.styleable.FlowLayout_columnSpacing, 0);
typedArray.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int expectHeight = 0;// 期望高度,累加 child 的 height
int lineWidth = 0;// 单行宽度,动态计算当前行的宽度。
int lineHeight = 0;// 单行高度,取该行中高度最大的view
float widthSpacing;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
// 测量子view 的宽高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
widthSpacing = i == 0 ? 0 : mColumnSpacing;
// 这里进行的是预判断。追加该child 后,行宽
// 若未超过提供的最大宽度,则行宽需要追加child 的宽度,并且计算该行的最大高度。
// 若超过提供的最大宽度,则需要追加该行的行高,并且更新下一行的行宽为当前child 的测量宽度。
if (lineWidth + widthSpacing + childWidth + getPaddingLeft() + getPaddingRight() <= widthSize) {// 未超过一行
// 追加行宽。
lineWidth += widthSpacing + childWidth;
// 不断对比,获取该行的最大高度
lineHeight = Math.max(lineHeight, childHeight);
} else {// 超过一行
// 更新最新一行的宽度为此child 的测量宽度
lineWidth = childWidth;
// 期望高度追加当前行的行高。
expectHeight += lineHeight + mRowSpacing;
}
}
// 这里添加的是最后一行的高度。因为上面是在换行时才追加的行高,在不需要换行时并没有追加行高,丢失了不满足换行条件下的行高。
// 举例说明:比如一行最多显示5个,但是当前只有1个,或者当前有6个的情况下,少了一行的行高。
expectHeight += lineHeight;
// 追加ViewGroup 的内边距
expectHeight += getPaddingTop() + getPaddingBottom();
setMeasuredDimension(widthSize, resolveSize(expectHeight, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int width = r - l;
int childLeftOffset = getPaddingLeft();// child view 的left偏移距离,用于记录左边位置。
int childTopOffset = getPaddingTop();// child view 的top偏移距离,用于记录顶部位置。
int lineHeight = 0;// 行高
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
//跳过View.GONE的子View
if (child.getVisibility() == View.GONE) {
continue;
}
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
if (childLeftOffset + childWidth + getPaddingRight() <= width) {// 该child 加入后未超过一行宽度。
// 行高取这一行中最高的child height
lineHeight = Math.max(lineHeight, childHeight);
} else {// 超过一行,换行显示。换行后的左侧偏移为初始值,顶部偏移为当前偏移量+当前行高
childLeftOffset = getPaddingLeft();
childTopOffset += lineHeight + mRowSpacing;
lineHeight = childHeight;
}
child.layout(childLeftOffset, childTopOffset, childLeftOffset + childWidth, childTopOffset + childHeight);
// 更新左侧偏移距离(+间距),即下一个child 的left
childLeftOffset += childWidth + mColumnSpacing;
}
}
public void setAdapter(BaseAdapter mAdapter) {
this.removeAllViews();
for (int i = 0; i < mAdapter.getCount(); i++) {
View view = mAdapter.getView(i, null, this);
this.addView(view);
}
requestLayout();
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
}
八.TextView文本的展开与折叠
原文地址:110.Android简单的自定义折叠文本,展开与隐藏TextView,自定义视图CollapsibleTextView,自定义View,文本展示_安卓写一段折叠文字视图的layout-优快云博客
public class CollapsibleTextView extends LinearLayout { /** * 默认(展开与隐藏)文本颜色 蓝色 */ private ForegroundColorSpan expandAndHideColorSpan = new ForegroundColorSpan(Color.parseColor("#3b76ec")); /** * 默认(内容文本)文本颜色 黑色 */ private ForegroundColorSpan textColorSpan = new ForegroundColorSpan(Color.parseColor("#000000")); /** * 文本显示最大行数,默认为1 */ private int maxLines = 1; /** * 标记 */ private boolean flag, flag2, flag3; /** * 文本 */ private String text; /** * 文本长度 */ private int length; private TextView mCollapsibleTextView; public CollapsibleTextView(Context context) { super(context); } public CollapsibleTextView(Context context, AttributeSet attrs) { super(context, attrs); View view = inflate(context, R.layout.layout_collapsible_textview, this); mCollapsibleTextView = (TextView) view.findViewById(R.id.mCollapsibleTextView); //设置textView可点击 mCollapsibleTextView.setMovementMethod(LinkMovementMethod.getInstance()); } /** * 对外提供暴露的方法,为设置文本内容、文本显示最大行数、(内容文本)文本颜色、(展开与隐藏)文本颜色 * * @param text 设置文本内容 * @param maxLines 设置文本显示最大行数 * @param textColor (内容文本)文本颜色 * @param expandAndHideColor (展开与隐藏)文本颜色 */ public void setText(String text, int maxLines, int textColor, int expandAndHideColor) { this.text = text; //赋值文本显示最大行数,最小是1 this.maxLines = Math.max(maxLines, 1); //得到字符串的长度 length = text.length(); //setText调用就设置flag为true flag = true; //flag3为false时 设置一次最大行数 if (!flag3) { //设置默认最大行数 mCollapsibleTextView.setMaxLines(maxLines); } //设置文本 mCollapsibleTextView.setText(text); //设置(内容文本)文本颜色 textColorSpan = new ForegroundColorSpan(ContextCompat.getColor(getContext(), textColor)); //设置(展开与隐藏)文本颜色 expandAndHideColorSpan = new ForegroundColorSpan(ContextCompat.getColor(getContext(), expandAndHideColor)); //重新请求布局 requestLayout(); } /** * 文本设置 * 设置展开或隐藏 * * @param count * @param str * @return */ private SpannableString getClickableSpan(int count, String str) { //新建一个spannableString(设置字体颜色,监听点击,字体大小) SpannableString span = new SpannableString(str); //count == 0时设置文本颜色并返回 if (count == 0) { //内容文本 设置字体大小 span.setSpan(new RelativeSizeSpan(1f), 0, str.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); //内容文本 设置字体可点击并监听 span.setSpan(new TextListener(), 0, str.length(), Spanned.SPAN_MARK_MARK); //内容文本 设置字体颜色 span.setSpan(textColorSpan, 0, str.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); //返回span return span; } int maxLines = mCollapsibleTextView.getMaxLines(); if (maxLines == this.maxLines) { //内容文本 设置字体大小 span.setSpan(new RelativeSizeSpan(1f), 0, count - 5, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); //内容文本 设置字体可点击并监听 span.setSpan(new TextListener(), 0, count - 5, Spanned.SPAN_MARK_MARK); //内容文本 设置字体颜色 span.setSpan(textColorSpan, 0, count - 5, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); //展开与隐藏 设置字体大小 span.setSpan(new RelativeSizeSpan(1.1f), count - 5, count, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); //展开与隐藏 设置字体可点击并监听 span.setSpan(new ExpandAndHideListener(), count - 5, count, Spanned.SPAN_MARK_MARK); //展开与隐藏 设置字体颜色 span.setSpan(expandAndHideColorSpan, count - 5, count, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } else { //内容文本 设置字体大小 span.setSpan(new RelativeSizeSpan(1f), 0, count, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); //内容文本 设置字体可点击并监听 span.setSpan(new TextListener(), 0, count, Spanned.SPAN_MARK_MARK); //内容文本 设置字体颜色 span.setSpan(textColorSpan, 0, count, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); //展开与隐藏 设置字体大小 span.setSpan(new RelativeSizeSpan(1.1f), count, count + 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); //展开与隐藏 设置字体可点击并监听 span.setSpan(new ExpandAndHideListener(), count, count + 3, Spanned.SPAN_MARK_MARK); //展开与隐藏 设置字体颜色 span.setSpan(expandAndHideColorSpan, count, count + 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } //返回span return span; } /** * 1.展开与隐藏的点击事件 * 2.(内容文本的点击事件 与 展开与隐藏文本的点击事件 可全部使用也可只使用一个 这里全部使用) */ private class ExpandAndHideListener extends ClickableSpan implements View.OnClickListener { //文字点击事件 @Override public void onClick(@NonNull View widget) { //flag设置并重新请求布局 flagSetting(); } @Override public void updateDrawState(@NonNull TextPaint ds) { super.updateDrawState(ds); ds.setUnderlineText(false); } } /** * 1.内容文本的点击事件 * 2.(内容文本的点击事件 与 展开与隐藏文本的点击事件 可全部使用也可只使用一个 这里全部使用) */ private class TextListener extends ClickableSpan implements View.OnClickListener { //文字点击事件 @Override public void onClick(@NonNull View widget) { //flag设置并重新请求布局 flagSetting(); } @Override public void updateDrawState(@NonNull TextPaint ds) { super.updateDrawState(ds); ds.setUnderlineText(false); } } /** * flag设置并重新请求布局 */ private void flagSetting() { flag = true; if (flag3) { //mCollapsibleTextView.getLineCount() <= defaultMaxLines返回true 反之false flag2 = mCollapsibleTextView.getLineCount() <= maxLines; //重新请求布局 requestLayout(); } } /** * 布局设置 * * @param changed * @param l * @param t * @param r * @param b */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); //flag为false返回 if (!flag) { return; } //设置为false flag = false; //flag2为true展开 if (flag2) { //展开状态 mCollapsibleTextView.setMaxLines(Integer.MAX_VALUE); //在原字符串后面加上“ 隐藏” String allText = text + " 隐藏"; SpannableString clickableSpan = getClickableSpan(length, allText); mCollapsibleTextView.setText(clickableSpan); flag3 = true; } else { //flag2为false 判断mCollapsibleTextView.getLineCount() > defaultMaxLines if (mCollapsibleTextView.getLineCount() > maxLines) { //收起状态 mCollapsibleTextView.setMaxLines(maxLines); //得到第一行最后一个字符的位置 int lineEnd = mCollapsibleTextView.getLayout().getLineEnd(maxLines - 1); //截取第一行的字符 替换最后五个字符为“...展开” String hiddenString = text.substring(0, lineEnd - 5) + "...展开"; SpannableString clickableSpan = getClickableSpan(lineEnd, hiddenString); mCollapsibleTextView.setText(clickableSpan); flag3 = true; } else { SpannableString clickableSpan = getClickableSpan(0, text); mCollapsibleTextView.setText(clickableSpan); } } } }xml文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/mCollapsibleTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@android:color/transparent" android:text="内容" android:textSize="14sp" /> </LinearLayout>应用:
CollapsibleTextView view = findViewById(R.id.mCollapsibleView); view.setText("传入文本内容",1, R.color.red, R.color.blue); view.setText("传入文本内容", 1, Color.parseColor("#000000"), Color.parseColor("#3b76ec")); 如此应用会报android.content.res.Resources$NotFoundException: Resource ID #0xff000000 错误