Android开发之 Fragment里嵌入高德地图
最近在做的项目里要用到地图,看了一下高德地图的API,最后决定就用高德地图,和平时不同,这次地图是要嵌在Fragment了,研究了一下网上的代码,最后实现了。下面说一下实现2D地图的方法。
1.先去高德地图官网注册Key,地址是http://lbs.amap.com/api/android-sdk/summary/;
2.根据说明下载所需的sdk.
3.配置工程
(1)添加key
在工程的“ AndroidManifest.xml ”文件如下代码中添加Key.
(1)添加权限
<pre><code><uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /></code></pre>
4.布局文件
<com.amap.api.maps2d.MapView android:id="@+id/map" android:layout_width="match_parent" android:layout_height="match_parent" />
5.实现
public class FragmentMap extends Fragment{ private static FragmentMap fragment = null; @ViewInject(R.id.map) private MapView mapView; private AMap aMap; private View mapLayout; public static Fragment newInstance() { if (fragment == null) { synchronized (FragmentMap.class) { if (fragment == null) { fragment = new FragmentMap(); } } } return fragment; } public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (mapLayout == null) { mapLayout = inflater.inflate(R.layout.fragment_map, null); ViewUtils.inject(this, mapLayout); mapView.onCreate(savedInstanceState);//必须写 if (aMap == null) { aMap = mapView.getMap(); } else { if (mapLayout.getParent() != null) { ((ViewGroup) mapLayout.getParent()).removeView(mapLayout); } } return mapLayout; } @Override public void onResume() { super.onResume(); mapView.onResume(); } /** * 方法必须重写 * map的生命周期方法 */ @Override public void onPause() { super.onPause(); mapView.onPause(); } /** * 方法必须重写 * map的生命周期方法 */ @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); mapView.onSaveInstanceState(outState); } /** * 方法必须重写 * map的生命周期方法 */ @Override public void onDestroy() { super.onDestroy(); mapView.onDestroy(); } }
效果图如下:
深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深入讲解一下View和ViewGroup。
深入理解Android中View
这回我们是深入到View内部,去研究View,去了解View的工作,抛弃其他因素,以便为以后能灵活的使用自定义空间打下一定的基础。希望有志同道合的朋友一起来探讨,深入Android内部,深入理解Android。
一、View是什么?
View是什么了,每个人都有自己的理解。在Android的官方文档中是这样描述的:这个类表示了用户界面的基本构建模块。一个View占用了屏幕上的一个矩形区域并且负责界面绘制和事件处理。View是用来构建用户界面组件(Button,Textfields等等)的基类。ViewGroup子类是各种布局的基类,它是个包含其他View(或其他ViewGroups)和定义这些View布局参数的容器。
其实说白了,View就是一个矩形区域,我们可以在这个区域上定义自己的控件。
注明:有对系统回调不太了解的回头看看回调,这样有助于对文章的理解。
二、View创建的一个概述:
在API中对View的回调流程有以个详细的描述,下面给出了原文翻译:(翻译有点仓促,大家多多包涵,有啥错的地方麻烦告知下我,我好改过来)
1.Creation :创建
----Constructors(构造器)
There is a form of the constructor that arecalled when the view is created from code and a form that is called when theview is inflated from a layout file. The second form should parse and apply anyattributes defined in the layout file.在构造器中有个一个表单当View从代码中创建和从Layout File 文件中创建时。第二个表单应该解析和应用一些在Layout File中定义的属性。
---- onFinishInflate()
Called after a view and all of itschildren has been inflated from XML.当View和他的所有子View从XML中解析完成后调用。
2. Layout :布局
----onMeasure(int, int)
Called to determine the size requirementsfor this view and all of its children. 确定View和它所有的子View要求的尺寸时调用
---- onLayout(boolean, int, int,int, int)
Calledwhen this view should assign a size and position to all of its children当这个View为其所有的子View指派一个尺寸和位置时调用
---- onSizeChanged(int, int, int,int)
Calledwhen the size of this view has changed.当这个View的尺寸改变后调用
3. Drawing :绘制
---- onDraw(Canvas)
Calledwhen the view should render its content.当View给定其内容时调用
4.Event processing :事件流程
----onKeyDown(int, KeyEvent)
Calledwhen a new key event occurs.当一个新的键按下时
---- onKeyUp(int, KeyEvent)
Calledwhen a key up event occurs.当一个键弹起时
----onTrackballEvent(MotionEvent)
Calledwhen a trackball motion event occurs.当滚迹球事件发生时。
----onTouchEvent(MotionEvent)
Calledwhen a touch screen motion event occurs.当一个触摸屏事件发生时。
5. Focus :焦点
---- onFocusChanged(boolean, int,Rect)
onFocusChanged(boolean,int, Rect)当View得到和失去焦点时调用
---- onWindowFocusChanged(boolean)
Called when the windowcontaining the view gains or loses focus.当Window包含的View得到或失去焦点时调用。
根据View里面方法调用流程的概述,我们来重写其中的几个回调方法来直观的了解下这个调用,具体代码这里就不贴了,代码见测试包:DEMO_View调用流程.rar,调用的log显示:
这样大家就对View的调用有了个大概的认识,下面将针对View的标志系统、View的的布局参数系统等做一个简单的描述。
三、View的标志(Flag)系统
在一个系统中往往使用标志来指示系统中的某些参数,这里对View的标志系统做一些简单的介绍,这样大家可以借鉴下,以后也可以用这种表示方法。
一般而言标志都是成对出现的也就是表示相反两个属性,对于这种属性的表示方法我们使用一位的0和1就可以表示。如果有多个成对属性,如果每对属性都用一个int值来标志是不方便的。这种情况通常是用一个int的各个位来分别表示每个标志,在处理器中有一个标志位就是采用这种方式设计的。
我们先来看看位运算。位运算符包括: 与(&)、非(~)、或(|)、异或(^)
&: 当两边操作数的位同时为1时,结果为1,否则为0。如1100&1010=1000
|: 当两边操作数的位有一边为1时,结果为1,否则为0。如1100|1010=1110
~: 0变1,1变0
^: 两边的位不同时,结果为1,否则为0.如1100^1010=0110
在View系统使用mViewFlags来表征这些属性,其设置的主要方法如下
void setFlag(int mask, int falg) { int old = mViewFlags;① mViewFlags = (mViewFlags & ~mask) | (mask & falg);② int changed = mViewFlags ^ old;// 获取改变的位,方法是对改变的位置1③ ... ... }
其中mask指的是标志位所在的位,falg表示的标志位。下面举个例子:
public static final int VISIBLE = 0x00000000; public static final int INVISIBLE = 0x00000004; public static final int GONE = 0x00000008; static final int VISIBILITY_MASK = 0x0000000C;
其中VISIBLE和INVISIBLE和GONE就是标志位,VISIBILITY_MASK是标志位所在的位,也就有VISIBLE+INVISIBLE+GON=VISIBILITY_MASK。看不懂的把上面四个转换为二进制就看出来了。
为什么要使用VISIBILITY_MASK?会不会有些多余呢?我们来看View中的计算公式:
mViewFlags = (mViewFlags & ~mask) | (mask & falg);②
其中mViewFlags & ~mask是用来将mViewFlags中表示该标志的位置零。mask & falg是用来获得标志位。举个例子:
假设mViewFlags的二进制表示为110000;flag为INVISIBLE我们将上面的标志位转换为二进制VISIBLE 0000、INVISIBLE 0100、GONE 1000、VISIBILITY_MASK 1100。
mViewFlags & ~mask=110000 & 0011 = 110000(上面所用的标志位占用的是最后四位,我们通过这个运算来将这个标志位置零)。
mask & falg = 1100 & 0100 =0100(获得标志)。
110000 | 0100(通过或运算来计算出最后的标志)。
一般而言:在多个同种类型的标志中,通常使用0来作为默认的标志。关于上面的标志系统的其他具体使用我们就不再深入,有兴趣的可以自行深入,有啥好的想法在群里分享下。
四、MeasureSpec
在View系统中,指定宽和高,以及指定布局的属性,是由MeasureSpec来封装的。下面是各个模式的标志位表示。
private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** * Measure specification mode: The parent has not imposed any constraint * on the child. It can be whatever size it wants. */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** * Measure specification mode: The parent has determined an exact size * for the child. The child is going to be given those bounds regardless * of how big it wants to be. */ public static final int EXACTLY = 1 << MODE_SHIFT; /** * Measure specification mode: The child can be as large as it wants up * to the specified size. */ public static final int AT_MOST = 2 << MODE_SHIFT;
在这个解析系统中是通过移位来存放更多的数据,现在每个数据标志位都向左移动了30位。这样表示一个View大小是很方便的,我们来看下面的方法:
public static int makeMeasureSpec(int size, int mode) { return size + mode; }
通过这个方法就可以制作一个含有两个参数的int值,这个参数包含一个mode标志和一个宽或高的表示。
我们通过如下方法来获取到mode:
public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); }
我们也可以用下面方法来获取高或宽的数据表示:
public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); }
五、几个重要方法简介
正如第二节写的那个调用流程一样,这几个重要的方法是系统回调是调用的,同样对于这几个方法也是自定义组件的重要的方法。
在这节里我们主要是了解这些方法的用途,以期在自定义组件时可以对这些方法得心应手。
5.1 onFinishInflate()
这个是当系统解析XML完成,并且将子View全部添加完成之后调用这个方法,我们通常重写这个方法,在这个方法中查找并获得子View引用,当然前提是这个View中有子View所以一般都是继承ViewGroup时用这个方法比较多,比如抽屉效果中:
@Override protected void onFinishInflate() { mHandle = findViewById(mHandleId); if (mHandle == null) { throw new IllegalArgumentException("The handle attribute is must refer to an" + " existing child."); } mHandle.setOnClickListener(new DrawerToggler()); mContent = findViewById(mContentId); if (mContent == null) { throw new IllegalArgumentException("The content attribute is must refer to an" + " existing child."); } mContent.setVisibility(View.GONE); }
通过重写这个方法来获取手柄的View和要显示内容的View。
5.2 onMeasure(int, int)
测量这个View的高和宽。通过调用这个方法来设置View的测量后的高和宽,其最终调用的方法是:
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= MEASURED_DIMENSION_SET; }
可见其最终是将高和宽保存在mMeasuredWidth、mMeasuredHeight这两个参数中。
其实调用onMeasure(int, int)的方法的不是系统,而是
public final voidmeasure(int widthMeasureSpec, int heightMeasureSpec)
这个才是系统回调的方法,然后通过这个方法调用onMeasure(int, int)方法,个人感觉这种设计就是把系统方法和用户可以重写的方法分离开,这样避免一些不必要的错误。
在这个方法中主要是用来初始化各个子View的布局参数,我们来看看抽屉中的实现:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) { throw new RuntimeException("SlidingDrawer cannot have UNSPECIFIED dimensions"); } final View handle = mHandle; measureChild(handle, widthMeasureSpec, heightMeasureSpec); if (mVertical) { int height = heightSpecSize - handle.getMeasuredHeight() - mTopOffset; mContent.measure(MeasureSpec.makeMeasureSpec(widthSpecSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); } else { int width = widthSpecSize - handle.getMeasuredWidth() - mTopOffset; mContent.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSpecSize, MeasureSpec.EXACTLY)); } setMeasuredDimension(widthSpecSize, heightSpecSize); }
刚才我们已经获取到mHandle和mContent的引用,因为onFinishInflate()方法调用在onMeasure(int, int)方法之前,所以这个不会出现nullPoint。我们可以看到在这个方法中主要就是为mHandle和mContent指定了布局参数。这里用到了MeasureSpec。
5.3 onLayout(boolean, int, int,int, int)
onLayout是用来指定各个子View的位置,这个方法和上面方法类似,也不是真正的系统回调函数,真正的回调函数是Layout。这个方法的使用主要在ViewGroup中。这里不再详述。我们在ViewGroup讲解时再去了解这个方法。
5.4 onSizeChanged(int, int, int,int)
这个是当View的大小改变时调用,这个也不再详述,基本上用的也比较少。
5.5 onDraw(android.graphics.Canvas)
这个方法相信大家都不会陌生了,在我以前的博客里也有这个方法的使用。当然那个比较入门,比较肤浅,呵呵。这里我们深入进去,类似于onMeasure(int, int),其实这个方法是由draw(Canvas)方法调用的。在这个方法中有一个对这个方法的描述:
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
我们可以看到:
首先是绘制背景
其次如果需要准备层之间的阴影
然后绘制内容(这个内容就是调用我们的onDraw方法)
再绘制children(dispatchDraw(canvas);)这个方法的调用主要实现在ViewGroup中,和继承ViewGroup的组件中。
如果需要绘制层之间的阴影。
绘制装饰,也就是scrollbars。
dispatchDraw(canvas);这也是一个重要的方法,用于绘制子组件用的。下面是抽屉中的实现方法。也比较简单,大家自行阅读下也就了解了。
@Override protected void dispatchDraw(Canvas canvas) { final long drawingTime = getDrawingTime(); final View handle = mHandle; final boolean isVertical = mVertical; drawChild(canvas, handle, drawingTime); if (mTracking || mAnimating) { final Bitmap cache = mContent.getDrawingCache(); if (cache != null) { if (isVertical) { canvas.drawBitmap(cache, 0, handle.getBottom(), null); } else { canvas.drawBitmap(cache, handle.getRight(), 0, null); } } else { canvas.save(); canvas.translate(isVertical ? 0 : handle.getLeft() - mTopOffset, isVertical ? handle.getTop() - mTopOffset : 0); drawChild(canvas, mContent, drawingTime); canvas.restore(); } } else if (mExpanded) { drawChild(canvas, mContent, drawingTime); } }
好了,这个就是View里面的内容,关于事件监听我们这里就不再详细描述,自定义组件的话,下面我们将会讲解深入ViewGroup,ViewGroup中也会去深化View中一些东西。
深入理解Android中ViewGroup
这回我们是深入到ViewGroup内部\,了解ViewGroup的工作,同时会阐述更多有关于View的相关知识。以便为以后能灵活的使用自定义空间打更近一步的基础。希望有志同道合的朋友一起来探讨,深入Android内部,深入理解Android。
一、ViewGroup是什么?
一个ViewGroup是一个可以包含子View的容器,是布局文件和View容器的基类。在这个类里定义了ViewGroup.LayoutParams类,这个类是布局参数的子类。
其实ViewGroup也就是View的容器。通过ViewGroup.LayoutParams来指定子View的参数。
ViewGroup作为一个容器,为了制定这个容器应有的标准所以为其指定了接口
public abstract class ViewGroup extends View implements ViewParent, ViewManager
这两个接口这里不研究,如果涉及到的话会带一下。ViewGroup有小4000行代码,下面我们一个模块一个模块分析。
二、ViewGroup这个容器
ViewGroup是一个容器,其采用一个数组来存储这些子View:
// Child views of this ViewGroup
private View[] mChildren;
由于是通过一个数组来存储View数据的,所以对于ViewGroup来说其必须实现增、删、查的算法。下面我们就来看看其内部实现。
2.1 添加View的算法
protected boolean addViewInLayout(View child, int index, LayoutParams params) { return addViewInLayout(child, index, params, false); } protected boolean addViewInLayout(View child, int index, LayoutParams params, boolean preventRequestLayout) { child.mParent = null; addViewInner(child, index, params, preventRequestLayout); child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN; return true; } private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) { ... addInArray(child, index); ... } private void addInArray(View child, int index) { ... }
上面四个方法就是添加View的核心算法的封装,它们是层层调用的关系。而我们通常调用的addView就是最终通过上面那个来最终达到添加到ViewGroup中的。
2.1.1 我们先来分析addViewInner方法:
首先是对子View是否已经包含到一个父容器中,主要的防止添加一个已经有父容器的View,因为添加一个拥有父容器的View时会碰到各种问题。比如记录本身父容器算法的问题、本身被多个父容器包含时更新的处理等等一系列的问题都会出现。
if (child.getParent() != null) {
throw new IllegalStateException("The specified child already has a parent. " +
"You must call removeView() on the child's parent first.");
}
然后就是对子View布局参数的处理。
调用addInArray来添加View
父View为当前的ViewGroup
焦点的处理。
当前View的AttachInfo信息,这个信息是用来在窗口处理中用的。Android的窗口系统就是用过AttachInfo来判断View的所属窗口的,这个了解下就行。详细信息设计到Android框架层的一些东西。
AttachInfo ai = mAttachInfo; if (ai != null) { boolean lastKeepOn = ai.mKeepScreenOn; ai.mKeepScreenOn = false; child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK)); if (ai.mKeepScreenOn) { needGlobalAttributesUpdate(true); } ai.mKeepScreenOn = lastKeepOn; }
View树改变的监听
if (mOnHierarchyChangeListener != null) {
mOnHierarchyChangeListener.onChildViewAdded(this, child);
}
子View中的mViewFlags的设置:
if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;
}
2.1.2 addInArray
这个里面的实现主要是有个知识点,以前也没用过arraycopy,这里具体实现就不多加描述了。
System.arraycopy(children, 0, mChildren, 0, index);
System.arraycopy(children, index, mChildren, index + 1, count - index);
2.2 移除View
移除View的几种方式:
移除指定的View。
移除从指定位置的View
移除从指定位置开始的多个View
移除所有的View
其中具体涉及到的方法就有好多了,不过最终对要删除的子View中所做的无非就是下列的事情:
如果拥有焦点则清楚焦点
将要删除的View从当前的window中解除关系。
设置View树改变的事件监听,我们可以通过监听OnHierarchyChangeListener事件来进行一些相应的处理。
从父容器的子容器数组中删除。
具体的内容这里就不一一贴出来了,大家回头看看源码就哦了。
2.3 查询
这个就简单了,就是直接从数组中取出就可以了:
public View getChildAt(int index) {
try {
return mChildren[index];
} catch (IndexOutOfBoundsException ex) {
return null;
}
}
分析到这儿,其实我们已经相当于分析了ViewGroup四分之一的代码了,呵呵。
三、onFinishInflate
我们一般使用View的流程是在onCreate中使用setContentView来设置要显示Layout文件或直接创建一个View,在当设置了ContentView之后系统会对这个View进行解析,然后回调当前视图View中的onFinishInflate方法。只有解析了这个View我们才能在这个View容器中获取到拥有Id的组件,同样因为系统解析完View之后才会调用onFinishInflate方法,所以我们自定义组件时可以onFinishInflate方法中获取指定子View的引用。
四、测量组件
在ViewGroup中提供了测量子组件的三个方法。
//1、measureChild(View, int, int),为子组件添加Padding protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
//2、measureChildren(int, int)根据指定的高和宽来测量所有子View中显示参数非GONE的组件。 protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
3、measureChildWithMargins(View, int, int, int, int)测量指定的子组件,为子组件添加Padding和Margin。
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
上面三个方法都是为子组件设置了布局参数。最终调用的方法是子组件的measure方法。在View中我们知道这个调用实际上就是设置了子组件的布局参数并且调用onMeasure方法,最终设置了View测量后的高度和宽度。
五、onLayout
这个函数是一个抽象函数,要求实现ViewGroup的函数必须实现这个函数,这也就是ViewGroup是一个抽象函数的原因。因为各种组件实现的布局方式不一样,而onLayout是必须被重载的函数。
@Override protected abstract void onLayout(boolean changed, int l, int t, int r, int b); 来看View中layout方法: public final void layout(int l, int t, int r, int b) { boolean changed = setFrame(l, t, r, b); if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); } onLayout(changed, l, t, r, b); mPrivateFlags &= ~LAYOUT_REQUIRED; } mPrivateFlags &= ~FORCE_LAYOUT; }
在这个方法中调用了setFrame方法,这个方法是用来设置View中的上下左右边距用的
protected boolean setFrame(int left, int top, int right, int bottom) { boolean changed = false; //....... if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) { changed = true; // Remember our drawn bit int drawn = mPrivateFlags & DRAWN; // Invalidate our old position invalidate(); int oldWidth = mRight - mLeft; int oldHeight = mBottom - mTop; mLeft = left; mTop = top; mRight = right; mBottom = bottom; mPrivateFlags |= HAS_BOUNDS; int newWidth = right - left; int newHeight = bottom - top; if (newWidth != oldWidth || newHeight != oldHeight) { onSizeChanged(newWidth, newHeight, oldWidth, oldHeight); } if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) { // If we are visible, force the DRAWN bit to on so that // this invalidate will go through (at least to our parent). // This is because someone may have invalidated this view // before this call to setFrame came in, therby clearing // the DRAWN bit. mPrivateFlags |= DRAWN; invalidate(); } // Reset drawn bit to original value (invalidate turns it off) mPrivateFlags |= drawn; mBackgroundSizeChanged = true; } return changed; } //我们可以看到如果新的高度和宽度改变之后会调用重新设置View的四个参数: //protected int mLeft; //protected int mRight; //protected int mTop; //protected int mBottom; //这四个参数指定了View将要布局的位置。而绘制的时候是通过这四个参数来绘制,所以我们在View中调用layout方法可以实现指定子View中布局。
六、ViewGroup的绘制。
ViewGroup的绘制实际上是调用的dispatchDraw,绘制时需要考虑动画问题,而动画的实现实际上就通过dispatchDraw来实现的。
我们不用理会太多的细节,直接看其绘制子组件调用的是drawChild方法,这个里面具体的东西就多了,涉及到动画效果的处理,如果有机会的话再写,我们只要知道这个方法的功能就行。
这里有个demo贴出其中的代码大家可以测试下。
public ViewGroup01(Context context) { super(context); Button mButton = new Button(context); mButton.setText("测试"); addView(mButton); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { View v = getChildAt(0); if(v != null) { v.layout(120, 120, 250, 250); } } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); View v = getChildAt(0); if(v != null) { drawChild(canvas, v, getDrawingTime()); } }
七、效果图片:
在上一篇已经写了嵌入高德地图,这里来说一下怎么实现定位,并显示定位的图标。
public class FragmentMap extends Fragment implements LocationSource, AMapLocationListener{ private static FragmentMap fragment = null; @ViewInject(R.id.map) private MapView mapView; private AMap aMap; private View mapLayout; private OnLocationChangedListener mListener; private LocationManagerProxy mAMapLocationManager; public static Fragment newInstance() { if (fragment == null) { synchronized (FragmentMap.class) { if (fragment == null) { fragment = new FragmentMap(); } } } return fragment; } public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (mapLayout == null) { mapLayout = inflater.inflate(R.layout.fragment_map, null); ViewUtils.inject(this, mapLayout); mapView.onCreate(savedInstanceState); if (aMap == null) { aMap = mapView.getMap(); aMap.setLocationSource(this);// 设置定位监听 aMap.getUiSettings().setMyLocationButtonEnabled(true);// 设置默认定位按钮是否显示 aMap.setMyLocationEnabled(true);// 设置为true表示显示定位层并可触发定位,false表示隐藏定位层并不可触发定位,默认是false // 自定义系统定位蓝点 MyLocationStyle myLocationStyle = new MyLocationStyle(); // 自定义定位蓝点图标 myLocationStyle.myLocationIcon(BitmapDescriptorFactory.fromResource(R.mipmap.content_btn_location)); // 将自定义的 myLocationStyle 对象添加到地图上 aMap.setMyLocationStyle(myLocationStyle); // 构造 LocationManagerProxy 对象 mAMapLocationManager = LocationManagerProxy.getInstance(getActivity()); } } else { if (mapLayout.getParent() != null) { ((ViewGroup) mapLayout.getParent()).removeView(mapLayout); } } return mapLayout; } @Override public void onAttach(Activity activity) { super.onAttach(activity); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void onResume() { super.onResume(); mapView.onResume(); } /** * 方法必须重写 * map的生命周期方法 */ @Override public void onPause() { super.onPause(); mapView.onPause(); deactivate(); } /** * 方法必须重写 * map的生命周期方法 */ @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); mapView.onSaveInstanceState(outState); } /** * 方法必须重写 * map的生命周期方法 */ @Override public void onDestroy() { super.onDestroy(); mapView.onDestroy(); } @Override public void onLocationChanged(AMapLocation aMapLocation) { if (mListener != null &amp;&amp; aMapLocation != null) { if (aMapLocation.getAMapException().getErrorCode() == 0) { mListener.onLocationChanged(aMapLocation);// 显示系统小蓝点 //获取位置信息 geoLat = aMapLocation.getLatitude(); geoLng = aMapLocation.getLongitude(); aMap.moveCamera(CameraUpdateFactory.zoomTo(14)); } } } @Override public void onLocationChanged(Location location) { } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public void onProviderEnabled(String provider) { } @Override public void onProviderDisabled(String provider) { } //激活定位 @Override public void activate(OnLocationChangedListener listener) { mListener = listener; if (mAMapLocationManager == null) { mAMapLocationManager = LocationManagerProxy.getInstance(getActivity()); //此方法为每隔固定时间会发起一次定位请求,为了减少电量消耗或网络流量消耗, //注意设置合适的定位时间的间隔,并且在合适时间调用removeUpdates()方法来取消定位请求 //在定位结束后,在合适的生命周期调用destroy()方法 //其中如果间隔时间为-1,则定位只定一次 mAMapLocationManager.requestLocationData(LocationProviderProxy.AMapNetwork, 60 * 1000, 10, this); } } //停止定位 @Override public void deactivate() { mListener = null; if (mAMapLocationManager != null) { mAMapLocationManager.removeUpdates(this); mAMapLocationManager.destroy(); } mAMapLocationManager = null; } }下文为各位重点介绍关于Android高德地图自定义Markers的例子,希望这篇文章能够让各位理解到Android高德地图自定义Markers的方法。
之前的博客里说了地图的嵌入和定位,今天就说说在地图上显示一些我们想要的。在地图中有自带的Markers(标记),但是它只显示一个椭圆的图标,一般是不符合我们的需求的,这样就要我们自己来自定义。首先标记有下面一些属性;
1.position(Required) 在地图上标记位置的经纬度值。参数不能为空。
2.title 当用户点击标记,在信息窗口上显示的字符串。
3.snippet 附加文本,显示在标题下方。
4.draggable 如果您允许用户可以自由移动标记,设置为“ true ”。默认情况下为“ false ”。
5.visible 设置“ false ”,标记不可见。默认情况下为“ true ”。
6.anchor图标摆放在地图上的基准点。默认情况下,锚点是从图片下沿的中间处。
7.perspective设置 true,标记有近大远小效果。默认情况下为 false。
8.可以通过Marker.setRotateAngle() 方法设置标记的旋转角度,从正北开始,逆时针计算。如设置旋转90度,Marker.setRotateAngle(90)
9.通过setFlat() 方法设置标志是否贴地显示
自定义图标通常由 BitmapDescriptor 设置。我们可以在类 BitmapDescriptorFactory 使用以下其中一种方法定义。
1.fromAsset(String assetName) 在 assets 目录中使用图像创建自定义标记。
2.fromBitmap (Bitmap image) 使用位图图像创建自定义标记。
3.fromFile (String path) 指定路径的文件创建自定义图标。
4.fromResource (int resourceId) 使用已经存在的资源创建自定义图标。
先看一下要实现的效果:
地图自带标记 实现效果
实现思路是:自定义布局,获取数据填入相应位置,然后将view转成Bitmap,调用AMap.addMarker(markerOptions) 方法添加到地图上。
自定义布局并填充数据:
for (int i = 0; i < positionEneityList.size(); i++) { if (positionEneityList.get(i).getType().equals("1")) { View view = View.inflate(getActivity(),R.layout.view_day, null); TextView tv_price = (TextView) view.findViewById(R.id.tv_price); TextView tv_price_status = (TextView) view.findViewById(R.id.tv_price_status); tv_price.setText(positionEneityList.get(i).getPrice()); tv_price_status.setText("元/时"); Bitmap bitmap = CommentActivity.convertViewToBitmap(view); drawMarkerOnMap(new LatLng(Double.parseDouble(positionEneityList.get(i).getLatitude()) , Double.parseDouble(positionEneityList.get(i).getLongitude())), bitmap, positionEneityList.get(i).getId()); } }
2.转成Bitmap:
//view 转bitmap public static Bitmap convertViewToBitmap(View view) { view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); view.buildDrawingCache(); Bitmap bitmap = view.getDrawingCache(); return bitmap; }
3.添加到地图上:
/** * 在地图上画marker * * @param point marker坐标点位置(example:LatLng point = new LatLng(39.963175, * 116.400244); ) * @param markerIcon 图标 * @return Marker对象 */ private Marker drawMarkerOnMap(LatLng point, Bitmap markerIcon, String id) { if (aMap != null && point != null) { Marker marker = aMap.addMarker(new MarkerOptions().anchor(0.5f, 1) .position(point) .title(id) .icon(BitmapDescriptorFactory.fromBitmap(markerIcon))); return marker; } return null; }
这样就实现了上述效果。
上一篇说了在地图上实现了自定义Markers,但是markers太多在地图上显示的就会密密麻麻,重叠覆盖,这里就介绍一下markers的聚合。先看一下封装好的聚合类。
public class MarkerClusterYellow { private Activity activity; private MarkerOptions options; private ArrayList<MarkerOptions>includeMarkers; private LatLngBounds bounds;// 创建区域 /** * * @param activity * @param firstMarkers * @param projection * @param gridSize * 区域大小参数 */ public MarkerClusterYellow(Activity activity, MarkerOptions firstMarkers, Projection projection, int gridSize) { options = new MarkerOptions(); this.activity = activity; Point point = projection.toScreenLocation(firstMarkers.getPosition()); Point southwestPoint = new Point(point.x - gridSize, point.y + gridSize); Point northeastPoint = new Point(point.x + gridSize, point.y - gridSize); bounds = new LatLngBounds( projection.fromScreenLocation(southwestPoint), projection.fromScreenLocation(northeastPoint)); options.anchor(0.5f, 0.5f).title(firstMarkers.getTitle()) .position(firstMarkers.getPosition()) .icon(firstMarkers.getIcon()) .snippet(firstMarkers.getSnippet()); includeMarkers = new ArrayList<MarkerOptions>(); includeMarkers.add(firstMarkers); } /** * 添加marker */ public void addMarker(MarkerOptions markerOptions) { includeMarkers.add(markerOptions);// 添加到列表中 } /** * 设置聚合点的中心位置以及图标 */ public void setpositionAndIcon(String text) { String id=""; int size = includeMarkers.size(); if (size == 1) { return; } double lat = 0.0; double lng = 0.0; String snippet = ""; for (MarkerOptions op : includeMarkers) { lat += op.getPosition().latitude; lng += op.getPosition().longitude; snippet += op.getTitle() + "\n"; id=id+op.getTitle()+","; } options.position(new LatLng(lat / size, lng / size));// 设置中心位置为聚集点的平均距离 options.title(id); options.snippet(snippet); int iconType = size / 2; switch (iconType) { default: options.icon(BitmapDescriptorFactory .fromBitmap(getViewBitmap(getView(size,text, R.mipmap.content_icon_positions_yellow)))); break; } } public LatLngBounds getBounds() { return bounds; } public MarkerOptions getOptions() { return options; } public void setOptions(MarkerOptions options) { this.options = options; } public View getView(int carNum,String text,int resourceId) { View view = activity.getLayoutInflater().inflate(R.layout.my_car_cluster_view, null); TextView carNumTextView = (TextView) view.findViewById(R.id.my_car_num); TextView tv_price = (TextView) view.findViewById(R.id.tv_price); TextView tv_price_status = (TextView) view.findViewById(R.id.tv_price_status); tv_price.setText(text); tv_price_status.setText("元/天"); tv_price.setTextColor(Color.parseColor("#FFBB18")); tv_price_status.setTextColor(Color.parseColor("#FFBB18")); LinearLayout myCarLayout = (LinearLayout) view.findViewById(R.id.my_car_bg); myCarLayout.setBackgroundResource(resourceId); carNumTextView.setText(String.valueOf(carNum)); return view; } /** * 把一个view转化成bitmap对象 */ public static Bitmap getViewBitmap(View view) {view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); view.buildDrawingCache(); Bitmap bitmap = view.getDrawingCache(); return bitmap; } }
在前一篇博客的基础上我们进行聚合,只需要做以下操作:
public class FragmentMap extends Fragment implements LocationSource, AMapLocationListener,OnCameraChangeListener { private static FragmentMap fragment = null; @ViewInject(R.id.map) private MapView mapView; private AMap aMap; private View mapLayout; private OnLocationChangedListener mListener; private LocationManagerProxy mAMapLocationManager; private List<PositionEneity> positionEneityList = new ArrayList<PositionEneity>(); private ArrayList<MarkerOptions> markerOptionsListYellow = new ArrayList<MarkerOptions>();// 所有的marker private ArrayList<MarkerOptions> markerOptionsListInViewYellow= new ArrayList<MarkerOptions>();// 视野内的marker String yellow=""; private int height;// 屏幕高度(px) private int width;// 屏幕宽度(px) private int gridSize = 50;// marker点区域大小 Handler handler = new Handler() { @Override public void handleMessage(android.os.Message msg) { super.handleMessage(msg); if (msg.what == 0) { resetMarks();// 更新markers } } }; public static Fragment newInstance() { if (fragment == null) { synchronized (FragmentMap.class) { if (fragment == null) { fragment = new FragmentMap(); } } } return fragment; } public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) { if (mapLayout == null) { mapLayout = inflater.inflate(R.layout.fragment_map, null); ViewUtils.inject(this, mapLayout); mapView.onCreate(savedInstanceState); DisplayMetrics dm = new DisplayMetrics(); getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm); width = dm.widthPixels; height = dm.heightPixels; if (aMap == null) { aMap = mapView.getMap(); aMap.setLocationSource(this);// 设置定位监听 aMap.getUiSettings().setMyLocationButtonEnabled(true);// 设置默认定位按钮是否显示 aMap.setMyLocationEnabled(true);// 设置为true表示显示定位层并可触发定位,false表示隐藏定位层并不可触发定位,默认是false } } else { if (mapLayout.getParent() != null) { ((ViewGroup) mapLayout.getParent()).removeView(mapLayout); } } aMap.setOnCameraChangeListener(this); return mapLayout; } /** * 获取视野内的marker 根据聚合算法合成自定义的marker 显示视野内的marker */ <strong> private void resetMarks() { // 开始刷新界面 Projection projection = aMap.getProjection(); Point p = null; markerOptionsListInViewYellow.clear(); // 获取在当前视野内的marker;提高效率 for (MarkerOptions mp : markerOptionsListYellow) { p = projection.toScreenLocation(mp.getPosition()); if (p.x < 0 || p.y < 0 || p.x > width || p.y > height) { // 不添加到计算的列表中 } else { markerOptionsListInViewYellow.add(mp); } } // 自定义的聚合类MarkerCluster ArrayList<MarkerClusterYellow> clustersMarkeryellow = new ArrayList<MarkerClusterYellow>(); for (MarkerOptions mp : markerOptionsListInViewYellow) { if (clustersMarkeryellow.size() == 0) { clustersMarkeryellow.add(new MarkerClusterYellow(getActivity(), mp, projection, gridSize));//gridSize 根据自己需求调整 } else { boolean isIn = false; for (MarkerClusterYellow cluster : clustersMarkeryellow) { if (cluster.getBounds().contains(mp.getPosition())) { cluster.addMarker(mp); isIn = true; break; } } if (!isIn) { clustersMarkeryellow.add(new MarkerClusterYellow(getActivity(), mp, projection, gridSize)); } } } // 先清除地图上所有覆盖物 aMap.clear(); for (MarkerClusterYellow markerClusterYellow : clustersMarkeryellow) { markerClusterYellow.setpositionAndIcon(yellow);// 设置聚合点的位置和icon aMap.addMarker(markerClusterYellow.getOptions());// 重新添加 } } private void initview() { for (int i = 0; i < positionEneityList.size(); i++) { if (positionEneityList.get(i).getType().equals("2")) { yellow=positionEneityList.get(i).getPrice(); View view01 = View.inflate(getActivity(),R.layout.view_everyday, null); TextView tv_price = (TextView) view01.findViewById(R.id.tv_price); TextView tv_price_status = (TextView) view01.findViewById(R.id.tv_price_status); tv_price.setText(positionEneityList.get(i).getPrice()); tv_price_status.setText("元/天"); Bitmap bitmap = CommentActivity.convertViewToBitmap(view01); <strong> markerOptionsListYellow.add(new MarkerOptions() .position(new LatLng(Double.parseDouble(positionEneityList.get(i).getLatitude()) , Double.parseDouble(positionEneityList.get(i).getLongitude()))).icon(BitmapDescriptorFactory.fromBitmap(bitmap)) .title(positionEneityList.get(i).getId())); } } } @Override public void onCameraChange(CameraPosition cameraPosition) { } @Override public void onCameraChangeFinish(CameraPosition cameraPosition) { handler.sendEmptyMessage(0);// 更新界面marker } }
定位和地图显示部分请参考之前的博客:
相关文章
- 下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
- overlay就是在地图上以另外一种形式浮现在地图上,常见的地图覆盖物为这三种类型,如:popup 弹窗、label标注信息、text文本信息等,接下来跟随小编看下openlayers6之地图覆盖物overlay详解,一起看看吧...2021-09-15
- 采用CSS覆盖的方法就可以了,但是官方是不允许这么做的...2013-10-13
Android开发中findViewById()函数用法与简化
findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20- 如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
- 夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
- 这篇文章主要介绍了vue+高德地图实现地图搜索及点击定位操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-09
- 为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
- 如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
- 深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
- 下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
- java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
- TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
- 这篇文章主要介绍了vscode搭建STM32开发环境的详细过程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-05-02
- 这篇文章主要介绍了React使用高德地图的实现示例(react-amap),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-18
android.os.BinderProxy cannot be cast to com解决办法
本文章来给大家介绍关于android.os.BinderProxy cannot be cast to com解决办法,希望此文章对各位有帮助呀。 Android在绑定服务的时候出现java.lang.ClassCastExc...2016-09-20- 这篇文章主要介绍了Android 实现钉钉自动打卡功能的步骤,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下...2021-03-15
- 下面我们来看一篇关于Android 开发之布局细节对比:RTL模式 ,希望这篇文章对各位同学会带来帮助,具体的细节如下介绍。 前言 讲真,好久没写博客了,2016都过了一半了,赶紧...2016-10-02
如何根据百度地图计算出两地之间的驾驶距离(两种语言js和C#)
以下是使用js代码实现百度地图计算两地距离,代码如下所示:<script src="js/jquery-1.9.0.js" type="text/javascript" language="javascript"></script><script language="javascript" type="text/javascript" src="js/...2015-10-30基于JavaScript实现高德地图和百度地图提取行政区边界经纬度坐标
本文给大家介绍javascript实现高德地图和百度地图提取行政区边界经纬度坐标的相关知识,本文实用性非常高,代码简单易懂,需要的朋友参考下吧...2016-01-24