一,前期基础知识储备
11月下旬已经走了一大半了,因为本月事情较多,还未来得及记录。这两天整理了一下过去一个月不完全懂的东西,分为两部分杂记,此为第二篇。
《11月杂记(一)——String拼接,Json读写,Xml读写,Hashmap使用,File存储》
《11月杂记(二)——SVG解析,RecyclerView删除列表,List统计+去重,RGB与HSB互转,获取图片像素RGB与图片主颜色》
二,上代码,具体实现
1.SVG解析
在博主之前的文章《有趣的自定义View —— SVG区域点击交互》中使用Document的方式解析过SVG图片,上篇文章是先将SVG图片转为Vector标签后,然后使用Document的方式解析。接下来,直接对SVG进行解析,不再进行转换。
以下是需要被解析的SVG信息:
<g id="block">
<path
style="fill: #e2b0b0"
d="M678.76,477l-.71,8.66,7.91,5.62c6.91-10.35,9.05-21.76,9.88-33.55l-1.43-1.36L668,473l3.67,5.73Z" />
<path
style="fill: #e2b0b0"
d="M672.24,510.17l11-16.58H675.8l-2.06,7.83-1.47.64-3.66-5.9-6.54,4.8Z" />
<path
style="fill: #e2b0b0"
d="M692.49,499.56c4.9-6.06,8.84-12.49,10.24-20.26,1.59-8.83,3.23-17.66,4.74-26.51a13.34,13.34,0,0,0-.36-3.57l-1.58-.31c-2,5.19-5.18,10.25-5.77,15.6-1.68,15.18-6.88,28.29-18.05,39-4.09,3.93-3.81,9.14-.95,12.1C684.76,510.1,688.38,504.63,692.49,499.56Z" />
<path
style="fill: #e2b0b0"
d="M641.79,469.59c13.07.73,25.87.75,36.83-8.5,4.08-3.44,9.35-5.48,14.07-8.17,2.46-1.4,4.9-2.84,7.35-4.26l-.92-1.81c-8.39,3.24-17.3,5.58-25.05,10-10.09,5.68-19.94,10.6-31.92,9.1-1-.12-2.1,1-3.16,1.54C639.91,468.18,640.81,469.54,641.79,469.59Z" />
</g>
接着是解析方法:将SVG视为标准XML,然后使用XmlPullParser的方式进行解析。
private void parseSvgXml() {
XmlPullParserFactory factory = null;
InputStream is = null;
try {
is = getAssets().open("block_path.xml"); //将xml文件导入输入流
factory = XmlPullParserFactory.newInstance(); //构造工厂实例
factory.setNamespaceAware(true); //设置xml命名空间为true
XmlPullParser xmlPullParser = factory.newPullParser(); //创建解析对象
xmlPullParser.setInput(is, "UTF-8"); //设置输入流和编码方式
int evtType = xmlPullParser.getEventType(); // 产生第一个时间
List<Path> pathList = new ArrayList<>();
List<String> styleList = new ArrayList<>();
List<String> dList = new ArrayList<>();
HashMap<Path, Boolean> pathInCenterMap = new HashMap<>();
/*END_DOCUMENT:XmlPullParser 定义的int常量,表示文档结束;*/
while (evtType != XmlPullParser.END_DOCUMENT) {
switch (evtType) {
case XmlPullParser.START_TAG:
String tagName = xmlPullParser.getName(); // getName():获取标签的名字,返回字符串,例如question;
if (tagName.equals("path")) {
int tagCount = xmlPullParser.getAttributeCount();
if (tagCount == 2) {
/*g - block 添加id时 第一次读到的是style 第二次是d*/
String q1 = xmlPullParser.getAttributeName(0);
String q2 = xmlPullParser.getAttributeValue(0);
Log.d(TAG, "parseSvgXml: style,," + tagCount + ",,," + q1 + ",," + q2);
styleList.add(q2);
String q3 = xmlPullParser.getAttributeName(1);
String q4 = xmlPullParser.getAttributeValue(1);
dList.add(q4);
Log.d(TAG, "parseSvgXml: d,," + tagCount + ",," + q3 + ",," + q4);
@SuppressLint("RestrictedApi")
Path path = PathParser.createPathFromPathData(q4);
pathList.add(path);
}
}
break;
case XmlPullParser.END_TAG:
break;
}
evtType = xmlPullParser.next();
}
this.styleList = styleList;
this.dList = dList;
this.pathList = pathList;
handleFalsePath(pathList);
} catch (IOException e) {
e.printStackTrace();
} catch (XmlPullParserException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
解析结果如下:
2.RecyclerView删除列表项
Activity内进行删除控制;
public void removeList(int position){
//删除数据源,移除集合中当前下标的数据
imgList.remove(position);
//刷新被删除的地方
adapter.notifyItemRemoved(position);
//刷新被删除数据,以及其后面的数据
adapter.notifyItemRangeChanged(position, imgList.size()-pos);
}
Adapter内进行删除控制;
public void removeList(int position){
imgList.remove(position);//删除数据源,移除集合中当前下标的数据
notifyItemRemoved(position);//刷新被删除的地方
notifyItemRangeChanged(position, getItemCount()); //刷新被删除数据,以及其后面的数据
}
推荐文章《Android-RecyclerView实现Item添加和删除》
3.List统计重复元素数量和去除重复元素
① 统计List元素种类数和各个元素的数量
private Map<String, Integer> allColorMap; // 装载所有颜色及对应颜色的个数
allColorMap = new HashMap<>();
allColorMap = ColorTypeUtils.frequencyOfListElements(colorBlockList);
/**
* java统计List集合中每个元素出现的次数
* 例如frequencyOfListElements(["111","111","222"])
* 则返回Map {"111"=2,"222"=1}
*/
public static Map<String, Integer> frequencyOfListElements(List<String> items) {
if (items == null || items.size() == 0) return null;
Map<String, Integer> map = new HashMap<String, Integer>();
for (String temp : items) {
Integer count = map.get(temp);
map.put(temp, (count == null) ? 1 : count + 1);
}
return map;
}
② 对List进行去重,去除重复的元素数;
public static List<String> removeDuplicate(List<String> list)
{
Set set = new LinkedHashSet<String>();
set.addAll(list);
list.clear();
list.addAll(set);
return list;
}
推荐文章:《Android 去除list集合中重复项的几种方法》
需要注意:利用HashSet进行去重时,可能会出现元素顺序打乱的情况,博主实际项目中出现过,引起了很大的麻烦。
4.RGB与HSB互转
HSB又称HSV,表示一种颜色模式:在HSB模式中,H(hues)表示色相,S(saturation)表示饱和度,B(brightness)表示亮度HSB模式对应的媒介是人眼。
HSB模式中S和B呈现的数值越高,饱和度明度越高,页面色彩强烈艳丽,对视觉刺激是迅速的,醒目的效果,但不益于长时间的观看。
色相(H,hue):在0~360°的标准色轮上,色相是按位置度量的。在通常的使用中,色相是由颜色名称标识的,比如红、绿或橙色。黑色和白色无色相。
饱和度(S,saturation):表示色彩的纯度,为0时为灰色。白、黑和其他灰色色彩都没有饱和度的。在最大饱和度时,每一色相具有最纯的色光。取值范围0~100%。
亮度(B,brightness或V,value):是色彩的明亮度。为0时即为黑色。最大亮度是色彩最鲜明的状态。取值范围0~100%
① RGB转HSB;
public float[] rgb2hsb(int rgbR, int rgbG, int rgbB) {
assert 0 <= rgbR && rgbR <= 255;
assert 0 <= rgbG && rgbG <= 255;
assert 0 <= rgbB && rgbB <= 255;
int[] rgb = new int[]{rgbR, rgbG, rgbB};
Arrays.sort(rgb);
int max = rgb[2];
int min = rgb[0];
float hsbB = max / 255.0f;
float hsbS = max == 0 ? 0 : (max - min) / (float) max;
float hsbH = 0;
if (max == rgbR && rgbG >= rgbB) {
hsbH = (rgbG - rgbB) * 60f / (max - min) + 0;
} else if (max == rgbR && rgbG < rgbB) {
hsbH = (rgbG - rgbB) * 60f / (max - min) + 360;
} else if (max == rgbG) {
hsbH = (rgbB - rgbR) * 60f / (max - min) + 120;
} else if (max == rgbB) {
hsbH = (rgbR - rgbG) * 60f / (max - min) + 240;
}
return new float[]{hsbH, hsbS, hsbB};
}
② HSB转RGB;
public int[] hsb2rgb(float h, float s, float v) {
assert Float.compare(h, 0.0f) >= 0 && Float.compare(h, 360.0f) <= 0;
assert Float.compare(s, 0.0f) >= 0 && Float.compare(s, 1.0f) <= 0;
assert Float.compare(v, 0.0f) >= 0 && Float.compare(v, 1.0f) <= 0;
float r = 0, g = 0, b = 0;
int i = (int) ((h / 60) % 6);
float f = (h / 60) - i;
float p = v * (1 - s);
float q = v * (1 - f * s);
float t = v * (1 - (1 - f) * s);
switch (i) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
default:
break;
}
return new int[]{(int) (r * 255.0), (int) (g * 255.0),
(int) (b * 255.0)};
}
注意,RGB数组和HSB数组精确度是不一样的。
int[] rgb
float[] newHsb
Arrays.toString(newHsb) - 查看hsb数组
5.获取图片某像素点位置的色值
// 确定像素点的位置
int xCoor = Integer.parseInt(pointBean.getxCor());
int yCoor = Integer.parseInt(pointBean.getyCor());
// 将此位置对应到Bitmap上,然后获取int类型的颜色数值
int color = bitmap.getPixel((int) (xCoor * ratio), (int) (yCoor * ratio));
// 将int类型的颜色值分别转换 得到R G B值
int r = Color.red(color);
int g = Color.green(color);
int b = Color.blue(color);
// 得到十六位进制的颜色值,如#ffggkk
int colorInfo = Color.rgb(r, g, b);
String colorRgb = ColorUtils.int2RgbString(colorInfo); // 使用Blankj工具类直接处理
注意最后一步中,将RGB值转为十六位进制的色值时,不要使用以下方法:
String r1 = Integer.toHexString(r); // 把R、G、B、A转为16进制
String g1 = Integer.toHexString(g);
String b1 = Integer.toHexString(b);
String colorStr = r1 + g1 + b1; // 十六进制的颜色字符串 b为单值时需要手动拼接一位数0
该方法最后一位值容易漏掉0,是需要自己判断然后手动拼接的。这里依旧是推荐Blankj的工具类进行处理。
6.获取图片的主颜色
根据图片的颜色显示不同的背景颜色,即显示图片的主色调。
① 使用谷歌官方的api中提供的方法Palette来实现;
推荐文章《Android Lollipop:使用Palette抽取图片主色调》
Github上亦有提供和图片加载结合的library使用起来比较简单;
② 遍历整张图片的像素点,将整个像素点的颜色值(去掉白色和纯黑色值)保存下来,选出颜色值最多的一个做为背景色;
推荐文章《Android 获取图片颜色》
7. 截取View做为图片
测试了三种方法,做了一个工具类,使用时,传入需要截取的View即可,返回的是一个Bitmap。
public class SaveStatusUtils {
/**
* 系统API 截取View 作为Bitmap返回
* 原View过大时崩溃
* @param v
* @return
*/
public static Bitmap getViewBp(View v) {
if (null == v) {
return null;
}
v.setDrawingCacheEnabled(true);
v.buildDrawingCache();
v.measure(View.MeasureSpec.makeMeasureSpec(v.getWidth(),
View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(
v.getHeight(), View.MeasureSpec.EXACTLY));
v.layout((int) v.getX(), (int) v.getY(),
(int) v.getX() + v.getMeasuredWidth(),
(int) v.getY() + v.getMeasuredHeight());
Bitmap b = Bitmap.createBitmap(v.getDrawingCache(), 0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
v.setDrawingCacheEnabled(false);
v.destroyDrawingCache();
return b;
}
/**
* 自己在Canvas上画图,并得到Canvas上的Bitmap
* 原View过大时 图片尺寸也很大 但图片内容显示正常
* @param v
* @return
*/
public static Bitmap loadBitmapFromView(View v) {
if (v == null) {
return null;
}
Bitmap screenshot;
screenshot = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_4444);
Canvas canvas = new Canvas(screenshot);
canvas.translate(-v.getScrollX(), -v.getScrollY());//我们在用滑动View获得它的Bitmap时候,获得的是整个View的区域(包括隐藏的),如果想得到当前区域,需要重新定位到当前可显示的区域
v.draw(canvas);// 将 view 画到画布上
return screenshot;
}
/**
* 获取一个 View 的缓存视图
*/
public static Bitmap getCacheBitmapFromView(View v) {
//第一种方案 返回的bitmap不为空
if (v.getLayoutParams().width <= 0 || v.getLayoutParams().height <= 0) {
return null;
}
Bitmap b = Bitmap.createBitmap(v.getLayoutParams().width, v.getLayoutParams().height, Bitmap.Config.ARGB_4444);
Canvas c = new Canvas(b);
v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
v.draw(c);
return b;
}
}
注意,一共有三种实现方式,第一种是使用系统API,图片本身过大时,会发生崩溃;第二第三种是借用Canvas进行绘制的方法,此种方法不会有崩溃发生。同时,只要控制好ImageView的大小和ScalType,截取View生成的图片是表现良好的。
另外就是,这是一个耗时操作,需要放入到线程中执行。
public void getViewBitmap(View view) {
new Thread(new Runnable() {
@Override
public void run() {
/*耗时操作*/
final Bitmap bitmap1 = SaveStatusUtils.getViewBp(setImageView);
appCompatImageView.post(new Runnable() {
@Override
public void run() {
appCompatImageView.setImageBitmap(bitmap1);
}
});
}
}).start();
}