AndroidカスタムView、エリア熱力マップ(各省のクリックインタフェースを備えている)
需要:熱力地図が必要で、中国の省のデータの対比を全面的に表示します
機能:完全な中国地図(ズーム可能、平行移動、クリック)
データカラーゾーンバー
各省の色を設定可能
各省はクリックイベントインタフェース(当該省をクリックし、黒線で当該省の枠を描く)を備えている
プロジェクトのアドレス:https://github.com/NoEndToLF/ChinaMapView
最終的な効果:
技術路線(簡単な技術構想、具体的な実現はGitHubのDemoを参照):
熱力図View技術:
1,assestディレクトリのSVGファイル(典型的なXML解析)を解析し,地図のすべてのPath(詳細はGitHubのDemoでutilパッケージのSVG解析クラスを参照)を解析し,このDemoで使用されるシミュレーションデータはutilパッケージのColorChangeUtilクラスにある
2解析したPathをChinaMapModelとProvinceModelオブジェクトにカプセル化するクリックイベント(クリック座標演算と位置決め、再描画、クリックされた省外枠黒、他の省外枠かデフォルトグレー) 両指スケーリングイベント(canvasのスケーリング後のMatrixをリセット) スライド移動イベント(canvasに対して平行移動後のMatrixをリセット) ここではすべてのイベントをScrollScaleGestureDetectorジェスチャーヘルプクラスにカプセル化し、主に問題を解決します. MotionEventに従ってイベント をスケーリングおよびパンする. MotionEventによるスケーリングの倍数と平行移動の距離 を得る.平行移動の境界点を制御し、平行移動が続くとビューが視界に消えることを防止する .は、拡大・縮小の最大倍数および最小倍数を制御し、設定された境界に移動した後の拡大・縮小により、境界 を超える結果となる.要するに、拡大または平行移動にかかわらず、設定された境界内に表示されることを保証する である.ズームおよびパン操作Matrixに基づいて、Viewの再描画のための は、MatrixのRectFにより座標換算を行い、クリックイベントの座標を初期化された座標系に換算する、クリック位置がある省内にあるか否かを判断するためのものである.判断のためのRegionは、初期座標系に基づく を含む省のクリックイベントである.ジェスチャーヘルプクラスのクリックイベントインタフェース クリックされた省のインタフェース ズームとパン、クリック後のViewペイント
1,パッケージMycolorAreaオブジェクト
開発をするには、着実に、日々の積み重ねが必要です.
機能:完全な中国地図(ズーム可能、平行移動、クリック)
データカラーゾーンバー
各省の色を設定可能
各省はクリックイベントインタフェース(当該省をクリックし、黒線で当該省の枠を描く)を備えている
プロジェクトのアドレス:https://github.com/NoEndToLF/ChinaMapView
最終的な効果:
技術路線(簡単な技術構想、具体的な実現はGitHubのDemoを参照):
熱力図View技術:
1,assestディレクトリのSVGファイル(典型的なXML解析)を解析し,地図のすべてのPath(詳細はGitHubのDemoでutilパッケージのSVG解析クラスを参照)を解析し,このDemoで使用されるシミュレーションデータはutilパッケージのColorChangeUtilクラスにある
2解析したPathをChinaMapModelとProvinceModelオブジェクトにカプセル化する
public class ChinaMapModel {
private float Max_x;//
private float Min_x;//
private float Max_y;//
private float Min_y;//
private List provinceslist;//
public float getMin_x() {
return Min_x;
}
public void setMin_x(float min_x) {
Min_x = min_x;
}
public float getMax_y() {
return Max_y;
}
public void setMax_y(float max_y) {
Max_y = max_y;
}
public float getMin_y() {
return Min_y;
}
public void setMin_y(float min_y) {
Min_y = min_y;
}
public float getMax_x() {
return Max_x;
}
public List getProvinceslist() {
return provinceslist;
}
public void setProvinceslist(List provinceslist) {
this.provinceslist = provinceslist;
}
public void setMax_x(float max_x) {
Max_x = max_x;
}
}
public class ProvinceModel {
private String name;//
private int color;//
private int linecolor;//
private List listpath;// path
private List regionList// path Region, path
private boolean isSelect;//
public boolean isSelect() {
return isSelect;
}
public void setSelect(boolean select) {
isSelect = select;
}
public List getRegionList() {
return regionList;
}
public void setRegionList(List regionList) {
this.regionList = regionList;
}
public int getLinecolor() {
return linecolor;
}
public void setLinecolor(int linecolor) {
this.linecolor = linecolor;
}
public List getListpath() {
return listpath;
}
public void setListpath(List listpath) {
this.listpath = listpath;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3,Viewを描く(まず大きさが合っている).初めて描く時、取った地図の最大横座標とviewの幅に従って拡大倍率を生成し、すべてのProvinceModel内のPath座標を拡大・縮小し、Viewの幅内で地図全体を描くことを保証し、拡大・縮小倍数によってViewの高さを動的に制御し、どの携帯電話でも適切に配置することを保証した.//
public ChinaMapView(Context context, AttributeSet attrs) {
super(context, attrs);
//
innerPaint=new Paint();
innerPaint.setColor(Color.BLUE);
innerPaint.setAntiAlias(true);
//
outerPaint=new Paint();
outerPaint.setColor(Color.GRAY);
outerPaint.setAntiAlias(true);
outerPaint.setStrokeWidth(1);
outerPaint.setStyle(Paint.Style.STROKE);
//
scrollScaleGestureDetector=new ScrollScaleGestureDetector(this,onScrollScaleGestureListener);
}
@Override
protected void onDraw(Canvas canvas) {
//
if (isFirst){
viewWidth=getWidth()-getPaddingLeft()-getPaddingRight();
// , map
if (map!=null){
map_scale=viewWidth/map.getMax_x();
}
// Path
scalePoints(canvas,map_scale);
isFirst=false;
}else{
//
scrollScaleGestureDetector.connect(canvas);
scrollScaleGestureDetector.setScaleMax(3);//
scrollScaleGestureDetector.setScalemin(1);//
// Map
drawMap(canvas);
}
super.onDraw(canvas);
}
// , map View
private void scalePoints(Canvas canvas,float scale) {
if (map.getProvinceslist().size()>0)
//map 4
map.setMax_x(map.getMax_x()*scale);
map.setMin_x(map.getMin_x()*scale);
map.setMax_y(map.getMax_y()*scale);
map.setMin_y(map.getMin_y()*scale);
for (ProvinceModel province:map.getProvinceslist()){
innerPaint.setColor(province.getColor());
List regionList=new ArrayList<>();
List pathList=new ArrayList<>();
for (Path p:province.getListpath()){
// Path ,
Path newpath=resetPath(p, scale, regionList);
pathList.add(newpath);
canvas.drawPath(newpath,innerPaint);
canvas.drawPath(newpath,outerPaint);
}
province.setListpath(pathList);
// path
province.setRegionList(regionList);
}
}
private Path resetPath(Path path,float scale,List regionList) {
List list=new ArrayList<>();
PathMeasure pathmesure=new PathMeasure(path,true);
float[] s=new float[2];
// Path
for (int i=0;i
4.ビューのイベントを処理する./** */
public class ScrollScaleGestureDetector {
private float beforeLength ,afterLength ; //
private float downX ; // x
private float downY ; // y
private float onMoveDownY ; // Y
private float onMoveDownX ; // X
private float scale_temp; //
private float downMidX,downMidY; //
private float offX,offY; // XY
// NONE: MOVE: ZOOM:
private int NONE ;
private int MOVE ;
private int ZOOM ;
private int mode = NONE;
private int scaleMax;//
private int scalemin;//
private View view;// View
private Matrix myMatrix; //
private final float[] matrixValues;//
private OnScrollScaleGestureListener onScrollScaleGestureListener;//
public interface OnScrollScaleGestureListener{
void onClick(float x,float y);
}
public ScrollScaleGestureDetector(View view,OnScrollScaleGestureListener onScrollScaleGestureListener){
this.view=view;
NONE=0;//
MOVE=1;//
ZOOM=2;//
mode=NONE;//
scale_temp=1;//
myMatrix=new Matrix();
matrixValues=new float[9]; // 9
this.onScrollScaleGestureListener=onScrollScaleGestureListener;
}
// , 1
public void setScaleMax(int scaleMax) {
if (scaleMax<=1){
this.scaleMax=1;
}else{
this.scaleMax=scaleMax;
}
}
// , 0
public void setScalemin(int scalemin) {
if (scalemin<=0){
this.scalemin=0;
}else{
this.scalemin=scalemin;
}
}
// View Canvas ,
public void connect(Canvas canvas) {
canvas.concat(myMatrix);
}
//
private void onTouchDown(MotionEvent event) {
// 1,
if(event.getPointerCount()==1){
mode = MOVE;
downX = event.getX();
downY = event.getY();
onMoveDownX=event.getX();
onMoveDownY=event.getY();
}
}
//
private void onPointerDown(MotionEvent event) {
if (event.getPointerCount() == 2) {
mode = ZOOM;
beforeLength = getDistance(event);
downMidX = getMiddleX(event);
downMidY=getMiddleY(event);
}
}
//
private void onTouchMove(MotionEvent event) {
//
if (mode == ZOOM) {
afterLength = getDistance(event);//
float gapLength = afterLength - beforeLength;//
// , ,
//
//
if (Math.abs(gapLength)>10&&beforeLength!=0){
if (gapLength>0){
if (scaleMax!=0) {
if (getScale() < scaleMax) {
scale_temp = afterLength / beforeLength;
} else {
scale_temp = scaleMax / getScale();
}
}else {
scale_temp = afterLength / beforeLength;
}
}else{
if (scalemin!=0){
if (getScale()>scalemin){
scale_temp=afterLength/beforeLength;
}else{
scale_temp = scalemin / getScale();
}
}else {
scale_temp=afterLength/beforeLength;
}
}
//
myMatrix.postScale(scale_temp, scale_temp, downMidX, downMidY);
// , , View ,
// , View
RectF rectF=getMatrixRectF();
if (rectF.left>=view.getWidth()/2){
myMatrix.postTranslate(view.getWidth()/2-rectF.left,0);
}
if (rectF.right<=view.getWidth()/2){
myMatrix.postTranslate(view.getWidth()/2-rectF.right,0);
}
if (rectF.top>=view.getHeight()/2){
myMatrix.postTranslate(0,view.getHeight()/2-rectF.top);
}
if (rectF.bottom<=view.getHeight()/2){
myMatrix.postTranslate(0,view.getHeight()/2-rectF.bottom);
}
view.invalidate();
beforeLength = afterLength;
}
}
//
else if(mode == MOVE){
//
offX = event.getX() - onMoveDownX;//X
offY = event.getY() - onMoveDownY;//y
RectF rectF=getMatrixRectF();
// View : ,
// Map , 、 、
//
if (rectF.left+offX>=view.getWidth()/2){
offX=view.getWidth()/2-rectF.left;
}
if (rectF.right+offX<=view.getWidth()/2){
offX=view.getWidth()/2-rectF.right;
}
if (rectF.top+offY>=view.getHeight()/2){
offY=view.getHeight()/2-rectF.top;
}
if (rectF.bottom+offY<=view.getHeight()/2){
offY=view.getHeight()/2-rectF.bottom;
}
//
myMatrix.postTranslate(offX,offY);
view.invalidate();
onMoveDownX=event.getX();
onMoveDownY=event.getY();
}
}
//
public boolean onTouchEvent(MotionEvent event){
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
onTouchDown(event);
break;
case MotionEvent.ACTION_POINTER_DOWN:
//
onPointerDown(event);
break;
case MotionEvent.ACTION_MOVE:
onTouchMove(event);
break;
case MotionEvent.ACTION_UP:
mode = NONE;
//down up 10,
if (Math.abs(event.getX()-downX)<10&&Math.abs(event.getY()-downY)<10){
if (onScrollScaleGestureListener!=null){
RectF rectF=getMatrixRectF();
// ,
PointF pf=new PointF((event.getX() -rectF.left)/getScale()
,(event.getY() -rectF.top)/getScale());
onScrollScaleGestureListener.onClick(pf.x,pf.y);
}
}
break;
//
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
}
return true;
}
//
private float getDistance(MotionEvent event){
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float)Math.sqrt(x * x + y * y);
}
// X
private float getMiddleX(MotionEvent event){
return (event.getX(1)+event.getX(0))/2;
}
// Y
private float getMiddleY(MotionEvent event){
return (event.getY(1)+event.getY(0))/2;
}
/**
*
*
* @return
*/
public final float getScale() {
myMatrix.getValues(matrixValues);
if (matrixValues[Matrix.MSCALE_X]==0){
return 1;
}else{
return matrixValues[Matrix.MSCALE_X];
}
}
/**
* Matrix
*/
private RectF getMatrixRectF() {
Matrix matrix = myMatrix;
RectF rect = new RectF();
rect.set(0, 0, view.getWidth(), view.getHeight());
matrix.mapRect(rect);
return rect;
}
}
5であるため、以下の部分private ScrollScaleGestureDetector.OnScrollScaleGestureListener onScrollScaleGestureListener=new ScrollScaleGestureDetector.OnScrollScaleGestureListener() {
@Override
public void onClick(float x, float y) {
//
for (ProvinceModel p:map.getProvinceslist()){
for (Region region:p.getRegionList()){
if (region.contains((int)x, (int)y)){
//
map.getProvinceslist().get(selectPosition).setSelect(false);
// map.getProvinceslist().get(selectPosition).setLinecolor(Color.GRAY);
// ,
p.setSelect(true);
p.setLinecolor(Color.BLACK);
// Activity ,
onProvinceClickLisener.onChose(p.getName());
invalidate();
return;
}
}
}
}
};
//
public interface onProvinceClickLisener{
public void onChose(String provincename);
}
// Map
private void drawMap(Canvas canvas) {
if (map.getProvinceslist().size()>0){
outerPaint.setStrokeWidth(1);
// , ,
for (int i=0;i
6,Activityで使用します.詳細はDemoのMainActivityを参照してください.
private void initMap() {
// SVG ,
myMap = new SvgUtil(this).getProvinces();
//
mapview.setMap(myMap);
}
mapview.setOnChoseProvince(new ChinaMapView.onProvinceClickLisener() {
@Override
public void onChose(String provincename) {
// ,listview
for (int i = 0; i < list.size(); i++) {
if (list.get(i).contains(provincename)) {
adapter.setPosition(i);
province_listview.setSelection(i);
break;
}
}
}
});
グラデーションバービューテクノロジ1,パッケージMycolorAreaオブジェクト
public class MycolorArea {
private int color;//
private String text;//
//get set
2,ColorViewの描画 public class ColorView extends View{
//
@Override
protected void onDraw(Canvas canvas) {
if (list==null)return;
if (list.size()>0){
int width_average=getWidth()/list.size();
for (int i=0;i
3,Activityで使用
//
setColorView();
private void setColorView() {
colorView_hashmap = new HashMap<>();
for (int i = 0; i < ColorChangeHelp.nameStrings.length; i++) {
String colors[] = ColorChangeHelp.colorStrings[i].split(",");
String texts[] = ColorChangeHelp.textStrings[i].split(",");
List list = new ArrayList<>();
for (int j = 0; j < colors.length; j++) {
MycolorArea c = new MycolorArea();
c.setColor(Color.parseColor(colors[j]));
c.setText(texts[j]);
list.add(c);
}
colorView_hashmap.put(ColorChangeHelp.nameStrings[i], list);
}
colorView.setList(colorView_hashmap.get(ColorChangeHelp.nameStrings[0]) );
}
開発をするには、着実に、日々の積み重ねが必要です.