android 自定义indicator,Android自定义Indicator
最近利用空闲时间,通过贝塞尔曲线写了一个Indicator,效果如下(视频转换gif效果不好)
完整代码传送门
对于原点的绘制及变化,需要对贝塞尔曲线及通过三阶贝塞尔曲线绘制圆形有初步了解,请参考以下文章
Bézier curve;Composite Bézier curve;Approximate a circle with cubic Bézier curves
对于贝塞尔曲线理论,没有较为扎实的数学基础还是很难理解的,笔者的高等数学早已把我抛弃了,很多符号都不记得,断断续续写了一个月才完成编写,主要是在分裂与粘合效果这碰了壁,很是汗颜~~~,好了直接上代码吧!
public class CustomIndicator extends View {
private static final double factor = 0.55191502449;
public static final int INDICATOR_TYPE_SCALE = 0;
public static final int INDICATOR_TYPE_GRADUAL = 1;
public static final int INDICATOR_TYPE_SPLIT = 2;
public static final int INDICATOR_TYPE_SCALE_AND_GRADUAL = 3;
private static final int DEFAULT_NORMAL_POINT_RADIUS = 8;
private static final int DEFAULT_SELECTED_POINT_RADIUS = 12;
private int heightMeasureSpec;
private Paint normalPointPaint;
private Paint selectedPointPaint;
private Paint targetPointPaint;
private float normalPointRadius;
private float selectedPointRadius;
private int pointInterval;
private int normalPointColor;
private int selectedPointColor;
private int indicatorType;
private int pointCount;
private List relativeControlPoints;
// private int selectedPagePosition;
private int currentPagePosition;
private int targetPagePosition;
private int width;
private int height;
private Path arcPath;
private Path splitArcPath;
private float translationFactor;
private PagerAdapter adapter;
private DataSetObserver dataSetObserver;
private static final int SPLIT_OFFSET = DisplayUtil.dp2px(10);
private static final float SPLIT_RADIUS_FACTOR = 1.4f;
@IntDef({INDICATOR_TYPE_SCALE, INDICATOR_TYPE_GRADUAL, INDICATOR_TYPE_SCALE_AND_GRADUAL, INDICATOR_TYPE_SPLIT})
@Retention(RetentionPolicy.SOURCE)
private @interface IndicatorType {
}
public CustomIndicator(Context context) {
this(context, null);
}
public CustomIndicator(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomIndicator(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
private void init(AttributeSet attrs) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CustomIndicator);
indicatorType = typedArray.getInt(R.styleable.CustomIndicator_indicatorType, 0);
normalPointRadius = typedArray.getDimensionPixelSize(R.styleable.CustomIndicator_normalPointRadius, DEFAULT_NORMAL_POINT_RADIUS);
selectedPointRadius = typedArray.getDimensionPixelSize(R.styleable.CustomIndicator_selectedPointRadius, indicatorType == INDICATOR_TYPE_GRADUAL ? DEFAULT_NORMAL_POINT_RADIUS : DEFAULT_SELECTED_POINT_RADIUS);
pointInterval = typedArray.getDimensionPixelSize(R.styleable.CustomIndicator_pointInterval, 20);
normalPointColor = typedArray.getColor(R.styleable.CustomIndicator_normalPointColor, Color.parseColor("#FFFFFF"));
selectedPointColor = typedArray.getColor(R.styleable.CustomIndicator_selectedPointColor, Color.parseColor("#11EEEE"));
typedArray.recycle();
normalPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
normalPointPaint.setStyle(Paint.Style.FILL);
normalPointPaint.setColor(normalPointColor);
if (indicatorType == INDICATOR_TYPE_GRADUAL || indicatorType == INDICATOR_TYPE_SCALE_AND_GRADUAL) {
selectedPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
selectedPointPaint.setStyle(Paint.Style.FILL);
selectedPointPaint.setColor(selectedPointColor);
targetPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
targetPointPaint.setStyle(Paint.Style.FILL);
targetPointPaint.setColor(selectedPointColor);
} else if (indicatorType == INDICATOR_TYPE_SPLIT) {
if (selectedPointRadius < normalPointRadius * SPLIT_RADIUS_FACTOR) {
selectedPointRadius = (int) (normalPointRadius * SPLIT_RADIUS_FACTOR);
}
if (pointInterval < SPLIT_RADIUS_FACTOR * normalPointRadius * 2 + SPLIT_OFFSET) {
pointInterval = (int) (SPLIT_RADIUS_FACTOR * normalPointRadius * 2 + SPLIT_OFFSET);
}
}
arcPath = new Path();
splitArcPath = new Path();
relativeControlPoints = new ArrayList<>();
// 初始化绘制 1/4 圆弧的三阶贝塞尔曲线控制点相对坐标(相对圆心)
for (int i = 0; i < 8; i++) {
float x;
float y;
switch (i) {
case 0: { // 右下P0
x = normalPointRadius;
y = (float) (normalPointRadius * factor);
break;
}
case 1: { // 右下P1
x = (float) (normalPointRadius * factor);
y = normalPointRadius;
break;
}
case 2: { // 左下P2
x = -(float) (normalPointRadius * factor);
y = normalPointRadius;
break;
}
case 3: { // 左下P3
x = -normalPointRadius;
y = (float) (normalPointRadius * factor);
break;
}
case 4: { // 左上P4
x = -normalPointRadius;
y = -(float) (normalPointRadius * factor);
break;
}
case 5: { // 左上P5
x = -(float) (normalPointRadius * factor);
y = -normalPointRadius;
break;
}
case 6: { // 右上P6
x = (float) (normalPointRadius * factor);
y = -normalPointRadius;
break;
}
default: { // 右上P7
x = normalPointRadius;
y = -(float) (normalPointRadius * factor);
break;
}
}
PointF pointF = new PointF(x, y);
relativeControlPoints.add(pointF);
}
}
public void setIndicatorType(@IndicatorType int type) {
if (type == indicatorType) {
return;
}
indicatorType = type;
if (indicatorType == INDICATOR_TYPE_GRADUAL || indicatorType == INDICATOR_TYPE_SCALE_AND_GRADUAL) {
if (selectedPointPaint == null) {
selectedPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
selectedPointPaint.setStyle(Paint.Style.FILL);
selectedPointPaint.setColor(selectedPointColor);
}
if (targetPointPaint == null) {
targetPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
targetPointPaint.setStyle(Paint.Style.FILL);
targetPointPaint.setColor(selectedPointColor);
}
} else if (indicatorType == INDICATOR_TYPE_SPLIT) {
if (selectedPointRadius < normalPointRadius * SPLIT_RADIUS_FACTOR) {
selectedPointRadius = (int) (normalPointRadius * SPLIT_RADIUS_FACTOR);
}
if (pointInterval < SPLIT_RADIUS_FACTOR * normalPointRadius * 2 + SPLIT_OFFSET) {
pointInterval = (int) (SPLIT_RADIUS_FACTOR * normalPointRadius * 2 + SPLIT_OFFSET);
}
}
postInvalidate();
}
public void bindViewPager(final ViewPager viewPager) {
if (viewPager != null) {
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
LogUtil.w("posInfo: " + currentPagePosition + " " + position + " " + positionOffset + " " + positionOffsetPixels);
// 动态计算当前页与目标页位置
if (positionOffsetPixels > 0) {
if (position < currentPagePosition) {
translationFactor = 1 - positionOffset;
currentPagePosition = position + 1;
targetPagePosition = position;
} else {
translationFactor = positionOffset;
currentPagePosition = position;
targetPagePosition = position + 1;
}
} else {
translationFactor = positionOffset;
currentPagePosition = position;
targetPagePosition = position;
}
postInvalidate();
}
@Override
public void onPageSelected(int position) {
// selectedPagePosition = position;
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
adapter = viewPager.getAdapter();
if (adapter != null && adapter instanceof RealPagerAdapterImp) {
pointCount = ((RealPagerAdapterImp) adapter).getRealCount();
measure(0, heightMeasureSpec);
// 监听数据变化
dataSetObserver = new DataSetObserver() {
@Override
public void onChanged() {
pointCount = ((RealPagerAdapterImp) adapter).getRealCount();
if (currentPagePosition >= pointCount) {
currentPagePosition = pointCount - 1;
targetPagePosition = currentPagePosition;
}
postInvalidate();
}
};
adapter.registerDataSetObserver(dataSetObserver);
} else {
throw new RuntimeException("please set adapter before bind this viewPager");
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
this.heightMeasureSpec = heightMeasureSpec;
if (pointCount > 0) {
width = (pointCount - 1) * pointInterval + 2 + 2 * (int) selectedPointRadius;
} else {
width = 0;
}
if (heightMeasureSpec == MeasureSpec.EXACTLY) {
height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
} else {
height = (int) (selectedPointRadius * 2) + 2;
}
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
if (pointCount <= 0) {
LogUtil.e("adapter size is 0");
return;
}
float pointRadius;
if (height > 0 && width > 0) {
if (indicatorType == INDICATOR_TYPE_SPLIT) {
drawSplitTypeIndicator(canvas);
return;
}
float centerX;
float centerY = height / 2;
float endX;
float endY;
float centerXOffset = selectedPointRadius;
for (int i = 0; i < pointCount; i++) {
centerX = i * pointInterval + centerXOffset;
// 根据ViewPager滑动动态调整当前选中点和目标点半径
if (i == currentPagePosition) {
pointRadius = normalPointRadius + (1 - translationFactor) * (selectedPointRadius - normalPointRadius);
} else if (i == targetPagePosition) {
pointRadius = normalPointRadius + (translationFactor) * (selectedPointRadius - normalPointRadius);
} else {
pointRadius = normalPointRadius;
}
arcPath.reset();
arcPath.moveTo(centerX + pointRadius, centerY);
for (int k = 0; k < relativeControlPoints.size() / 2; k++) {
switch (k) {
case 0: { // 相对于圆心,第一象限
endX = centerX;
endY = centerY + pointRadius;
break;
}
case 1: { // 相对于圆心,第二象限
endX = centerX - pointRadius;
endY = centerY;
break;
}
case 2: { // 相对于圆心,第三象限
endX = centerX;
endY = centerY - pointRadius;
break;
}
default: { // 相对于圆心,第四象限
endX = centerX + pointRadius;
endY = centerY;
break;
}
}
float controlPointX1;
float controlPointY1;
float controlPointX2;
float controlPointY2;
if (i == currentPagePosition || i == targetPagePosition) {
// 控制点坐标根据ViewPager滑动做相应缩放
float stretchFactor = pointRadius / normalPointRadius;
controlPointX1 = centerX + relativeControlPoints.get(k * 2).x * stretchFactor;
controlPointY1 = centerY + relativeControlPoints.get(k * 2).y * stretchFactor;
controlPointX2 = centerX + relativeControlPoints.get(k * 2 + 1).x * stretchFactor;
controlPointY2 = centerY + relativeControlPoints.get(k * 2 + 1).y * stretchFactor;
} else {
controlPointX1 = centerX + relativeControlPoints.get(k * 2).x;
controlPointY1 = centerY + relativeControlPoints.get(k * 2).y;
controlPointX2 = centerX + relativeControlPoints.get(k * 2 + 1).x;
controlPointY2 = centerY + relativeControlPoints.get(k * 2 + 1).y;
}
arcPath.cubicTo(controlPointX1, controlPointY1, controlPointX2, controlPointY2, endX, endY);
if (indicatorType == INDICATOR_TYPE_GRADUAL || indicatorType == INDICATOR_TYPE_SCALE_AND_GRADUAL) {
int alpha = (int) (translationFactor * 255);
if (i == currentPagePosition) {
selectedPointPaint.setAlpha(255 - alpha);
normalPointPaint.setAlpha(alpha);
canvas.drawPath(arcPath, normalPointPaint);
canvas.drawPath(arcPath, selectedPointPaint);
} else if (i == targetPagePosition) {
targetPointPaint.setAlpha(alpha);
normalPointPaint.setAlpha(255 - alpha);
canvas.drawPath(arcPath, normalPointPaint);
canvas.drawPath(arcPath, targetPointPaint);
} else {
normalPointPaint.setAlpha(255);
canvas.drawPath(arcPath, normalPointPaint);
}
} else if (indicatorType == INDICATOR_TYPE_SCALE) {
canvas.drawPath(arcPath, normalPointPaint);
}
}
}
}
}
// 绘制分裂效果Indicator
private void drawSplitTypeIndicator(Canvas canvas) {
float centerX;
float centerY = height / 2;
float endX;
float endY;
float centerXOffset = selectedPointRadius;
float selectedSplitEndX = 0;
float selectedSplitEndY = 0;
// 控制分裂圆形半径的系数
float splitRadiusFactor;
if (translationFactor * pointInterval <= 2 * normalPointRadius) {
splitRadiusFactor = translationFactor * pointInterval / (normalPointRadius * 2);
splitRadiusFactor = (float) Math.log(1 + (Math.E - 1) * splitRadiusFactor);
} else if (translationFactor * pointInterval > pointInterval - 2 * normalPointRadius) {
splitRadiusFactor = (pointInterval - translationFactor * pointInterval) / (2 * normalPointRadius);
splitRadiusFactor = (float) Math.log((Math.E - 1) * splitRadiusFactor + 1);
} else {
splitRadiusFactor = 1;
}
// 动态调整分裂圆形的半径
float selectedSplitPointRadius = normalPointRadius + (1 - splitRadiusFactor) * (selectedPointRadius - normalPointRadius);
// 分裂圆形的滑动偏移量
float selectedSplitPointCenterXOffset = currentPagePosition < targetPagePosition ? translationFactor * (pointInterval) : -translationFactor * (pointInterval);
for (int i = 0; i < pointCount; i++) {
centerX = i * pointInterval + centerXOffset;
arcPath.reset();
arcPath.moveTo(centerX + normalPointRadius, centerY);
splitArcPath.reset();
if (i == currentPagePosition) {
float splitOffset = getSplitOffset();
//
if (currentPagePosition == targetPagePosition) {
splitArcPath.moveTo(centerX + selectedSplitPointCenterXOffset + selectedSplitPointRadius, centerY);
} else if (currentPagePosition > targetPagePosition) {
splitArcPath.moveTo(centerX + selectedSplitPointCenterXOffset + selectedSplitPointRadius + splitOffset, centerY);
} else {
float currentX = centerX + selectedSplitPointCenterXOffset + selectedSplitPointRadius;
// 根据粘合偏移量控制分裂圆形的起点(初始滑动分裂阶段为零,后半段粘合时有效)
splitArcPath.moveTo(currentX + getCurrentBondingOffset(currentX - centerX), centerY);
// 根据滑动分裂偏移量调整当前圆形的起点
arcPath.moveTo(centerX + normalPointRadius + splitOffset, centerY);
}
}
if (i == targetPagePosition && currentPagePosition > targetPagePosition) {
arcPath.moveTo(centerX + normalPointRadius + getTargetBondingOffset(), centerY);
}
for (int k = 0; k < relativeControlPoints.size() / 2; k++) {
switch (k) {
case 0: {
endX = centerX;
endY = centerY + normalPointRadius;
if (i == currentPagePosition) {
selectedSplitEndX = centerX + selectedSplitPointCenterXOffset;
selectedSplitEndY = centerY + selectedSplitPointRadius;
}
break;
}
case 1: {
endX = centerX - normalPointRadius;
endY = centerY;
if (i == currentPagePosition) {
selectedSplitEndX = centerX + selectedSplitPointCenterXOffset - selectedSplitPointRadius;
selectedSplitEndY = centerY;
if (currentPagePosition != targetPagePosition) {
float offset = getSplitOffset();
if (currentPagePosition > targetPagePosition) {
endX -= offset;
selectedSplitEndX -= getCurrentBondingOffset(centerX - selectedSplitEndX);
} else {
selectedSplitEndX -= offset;
}
}
}
if (i == targetPagePosition && currentPagePosition < targetPagePosition) {
endX -= getTargetBondingOffset();
}
break;
}
case 2: {
endX = centerX;
endY = centerY - normalPointRadius;
if (i == currentPagePosition) {
selectedSplitEndX = centerX + selectedSplitPointCenterXOffset;
selectedSplitEndY = centerY - selectedSplitPointRadius;
}
break;
}
default: {
endX = centerX + normalPointRadius;
endY = centerY;
if (i == currentPagePosition) {
selectedSplitEndX = centerX + selectedSplitPointCenterXOffset + selectedSplitPointRadius;
selectedSplitEndY = centerY;
if (currentPagePosition != targetPagePosition) {
float offset = getSplitOffset();
if (currentPagePosition < targetPagePosition) {
endX += offset;
selectedSplitEndX += getCurrentBondingOffset(selectedSplitEndX - centerX);
} else {
selectedSplitEndX += offset;
}
}
}
if (i == targetPagePosition && currentPagePosition > targetPagePosition) {
endX += getTargetBondingOffset();
}
break;
}
}
float controlPointX1 = centerX + relativeControlPoints.get(k * 2).x;
float controlPointY1 = centerY + relativeControlPoints.get(k * 2).y;
float controlPointX2 = centerX + relativeControlPoints.get(k * 2 + 1).x;
float controlPointY2 = centerY + relativeControlPoints.get(k * 2 + 1).y;
arcPath.cubicTo(controlPointX1, controlPointY1, controlPointX2, controlPointY2, endX, endY);
canvas.drawPath(arcPath, normalPointPaint);
if (i == currentPagePosition) {
float stretchFactor = selectedSplitPointRadius / normalPointRadius;
controlPointX1 = centerX + selectedSplitPointCenterXOffset + relativeControlPoints.get(k * 2).x * stretchFactor;
controlPointY1 = centerY + relativeControlPoints.get(k * 2).y * stretchFactor;
controlPointX2 = centerX + selectedSplitPointCenterXOffset + relativeControlPoints.get(k * 2 + 1).x * stretchFactor;
controlPointY2 = centerY + relativeControlPoints.get(k * 2 + 1).y * stretchFactor;
splitArcPath.cubicTo(controlPointX1, controlPointY1, controlPointX2, controlPointY2, selectedSplitEndX, selectedSplitEndY);
canvas.drawPath(splitArcPath, normalPointPaint);
}
}
}
}
// 动态调用该方法,获取分裂效果贝塞尔曲线偏移量
private float getSplitOffset() {
float participantX = translationFactor * pointInterval;
if (participantX > SPLIT_RADIUS_FACTOR * normalPointRadius * 2) {
participantX = 0;
}
float offset;
float offsetFactor;
offsetFactor = SPLIT_RADIUS_FACTOR - participantX / (2 * normalPointRadius);
offsetFactor = offsetFactor > 2 * (SPLIT_RADIUS_FACTOR - 1) ? 0 : offsetFactor;
offset = offsetFactor * participantX;
return offset;
}
// 动态调用该方法,获取分粘合果贝塞尔曲线偏移量
private float getTargetBondingOffset() {
float participantX = translationFactor * pointInterval - (pointInterval - SPLIT_RADIUS_FACTOR * normalPointRadius * 2);
if (participantX < 0) {
return 0;
}
float offset;
float offsetFactor;
offsetFactor = SPLIT_RADIUS_FACTOR - participantX / (2 * normalPointRadius);
offset = offsetFactor * participantX;
return offset;
}
private float getCurrentBondingOffset(float currentOffsetX) {
float participantX = translationFactor * pointInterval - (pointInterval - SPLIT_RADIUS_FACTOR * normalPointRadius * 2);
if (participantX < 0) {
return 0;
}
float offset;
float offsetFactor;
offsetFactor = SPLIT_RADIUS_FACTOR - participantX / (2 * normalPointRadius);
offset = offsetFactor * participantX;
if (offset + currentOffsetX > pointInterval + normalPointRadius) {
offset -= offset + currentOffsetX - pointInterval - normalPointRadius;
}
if (offset < 0) {
offset = 0;
}
return offset;
}
@Override
protected void onDetachedFromWindow() {
if (adapter != null && dataSetObserver != null) {
adapter.unregisterDataSetObserver(dataSetObserver);
}
super.onDetachedFromWindow();
}
}
本示例的圆形绘制,是从右下方1/4圆弧开始(相对于圆心的第一象限),顺时针依次绘制。绘制三阶贝塞尔曲线,需要两个控制点,一个起点与一个终点,绘制圆形需要依次绘制四个三阶贝塞尔曲线,即需要八个控制点,而示例代码中的relativeControlPoints列表存储了绘制圆形所需要的八个控制点相对于圆心的坐标。对于绘制圆形所需的控制点坐标的由来,强烈建议您参考这篇文章:Approximate a circle with cubic Bézier curves,并注意数学上坐标系与Android坐标系的差别,示意图如下(由于制图工具限制,会有偏差,仅做示意):
对于圆形大小的控制,其重点在于根据滑动偏移系数动态调整半径大小及控制点坐标的大小,二者做相同比例缩放即可;对于分裂与粘合效果,其重点在于根据滑动偏移系数分别动态调整当前圆形、目标圆形及分裂圆形的对应圆弧的起点与终点的偏移量。示例代码中有相关注释,细节部分请参考代码。
android 自定义indicator,Android自定义Indicator相关推荐
- android 自定义banner,Android项目 引入Banner开源库(轮播图)
Banner开源库是什么? Banner 是 Android广告图片轮播控件,内部基于ViewPager2实现,Indicator和UI都可以自定义. 怎么使用 Banner 开源库? 1.在项目\a ...
- [Android]Tablayout:修改指示器indicator的宽度
一.问题描述: 最近接触到了Tablayout,需求是要把Tablayout的下划线宽度缩短,或者说使其可以进行自定宽度. 百度上面大多数利用反射,(具体可百度查询),这种方法确实可以把下划线变短,但 ...
- android炫酷的自定义view,Android自定义View实现炫酷进度条
本文实例为大家分享了Android实现炫酷进度条的具体代码,供大家参考,具体内容如下 下面我们来实现如下效果: 第一步:创建attrs文件夹,自定义属性: 第二步:自定义View: /** * Cre ...
- android单线字体,Android自定义字体
在main文件夹下,新建assets/fonts文件,添加.otf文件 image.png 字体工具类 import android.app.Application; import android.g ...
- android 自定义命名空间,Android自定义ActionBar实例
本文实例讲述了android自定义actionbar的实现方法.分享给大家供大家参考.具体实现方法如下: android 3.0及以上已经有了actionbar的api,可以通过引入support p ...
- android 自定义图片容器,Android应用开发中自定义ViewGroup视图容器的教程
一.概述在写代码之前,我必须得问几个问题: 1.ViewGroup的职责是啥?ViewGroup相当于一个放置View的容器,并且我们在写布局xml的时候,会告诉容器(凡是以layout为开头的属性, ...
- android sqlite自定义函数,Android中自定义一个View的方法详解
本文实例讲述了Android中自定义一个View的方法.分享给大家供大家参考,具体如下: Android中自定义View的实现比较简单,无非就是继承父类,然后重载方法,即便如此,在实际编码中难免会遇到 ...
- Android Paint应用之自定义View实现进度条控件
在上一篇文章<Android神笔之Paint>学习了Paint的基本用法,但是具体的应用我们还没有实践过.从标题中可知,本文是带领读者使用Paint,自定义一个进度条控件. 上图就是本文要 ...
- android 自定义图形,Android自定义View之图形图像(模仿360的刷新球自定
概述: 360安全卫士的那个刷新球(姑且叫它刷新球,因为真的不知道叫什么好,不是dota里的刷新球!!),里面像住了水一样,生动可爱,看似简单,写起来不太简单,本例程只是实现了它的部分功能而已,说实话 ...
- Android开源控件ViewPager Indicator的使用方法
1月16日厦门 OSC 源创会火热报名中,奖品多多哦 摘要 Android开源控件ViewPager Indicator的使用介绍 ViewPagerIndicator 目录[-] 1. V ...
最新文章
- parcel react_如何使用Parcel捆绑React.js应用程序
- HI3519V101调试记录
- 关于 sql server 基本使用的建议
- springcloud 微服务 分布式 Activiti6 工作流 vue.js html 跨域 前后分离
- ES6箭头函数和模板字符串
- java semaphore 等待时间_一个java同步工具类Semaphore的详解
- 在Java错误产生之前对其进行处理的新方法
- 笨方法“学习python笔记之random
- android simple-xml,使用Maven构建Android项目-dexer在simple-xml依赖项上失败
- php有哪些优化技巧
- PHP无限极分类巧用引用生成树
- 浅析我对代码规范的理解
- MySQL 中隔离级别 RC 与 RR 的区别
- VC连接SQL2005
- mysql可视化界面数据导出_MySQL 使用可视化工具导出与导入数据
- 使用Altium Designer绘制电路原理图
- 扫盲贴-万能密码的原理
- Android中什么是Dex文件
- 澳洲移民 技术移民_满足COVID-19期间移民对语言访问的需求
- 小程序加密解密完成版