我从对象的 ArrayList 添加标记到 googleMap。大约有250个标记;我什至必须将它们转换为位图以自定义它们。这是一项相当耗费资源的任务。但它严重阻塞了我的 UI 线程。
这是我的做法:
final HashMap<Marker, NearLocation> markerIdMap = new HashMap<Marker, NearLocation>();
for (final NearLocation result : MainActivity.nearLocationList) {
// Do all the hard work here
}
在加载 map 并在生成 map 时填充它们之后,我如何以某种方式动态地执行此操作?我不确定我是否可以通过在后台执行一些工作来做到这一点,然后在标记完成后将其移至 UI 线程进行添加。
我知道如何使用 AsyncTask 单独执行此操作。虽然不确定,但我正在循环...
最佳答案
据我所知,添加标记不能在 UI 线程之外完成。
您可以做的是在后台执行所有准备工作(创建标记、转换为位图等)。为了在添加标记时节省 UI 线程,您可以放大并使用 https://code.google.com/p/android-maps-extensions/仅显示可见标记或群集标记以降低数量,尽管 250 不是很多 imo。
这是关于该主题的 SO 答案:Add markers dynamically on Google Maps v2 for Android
我有一个应用程序,它有大约 4500 个标记,使用第一种方法运行得相当好(只要它不是快速缩小)。应该注意的是,在这里我选择制作一个带有进度条的启动画面,在用户打开 map 之前准备好所有标记。
如果你想要一个简单的机制来选择周围的标记而不使用第三部分库,你可以这样做:Android Maps v2 - animate camera to include most markers
刚才想到的一个想法,如果标记的创建真的那么昂贵,那就是添加 EventBus https://github.com/greenrobot/EventBus到您的项目。
使用 EventBus,您可以在 onEventAsync() 方法中对标记进行长时间运行准备。在该方法中,每当标记准备就绪时,将新标记发布到 UI EventBus.getDefault().post(marker) 并在 onEventMainThread(Marker marker) 中捕获它。在这里,您可以将标记保存在所有准备好的标记列表中,如果 map 当前打开,请添加它。
这是我在前面提到的应用程序中用来准备标记的一些代码。它用于在消防部门使用的应用程序中显示消防栓。第一次启动时,所有消防栓都是从一组 CSV 文件中读取的,并为所有约 4500 个消防栓创建了 MarkerOptions。毫无疑问,很多代码对你没有用,只是留下它,以防你能从中受益:
private List<HydrantHolder> mHydrants;
private Map<HydrantType, List<MarkerOptions>> mMarkers;
private class ReadHydrantsFiles extends
AsyncTask<Void, Integer, List<HydrantHolder>> {
File[] hydrantsFiles = new File[0];
// Before running code in separate thread
@Override protected void onPreExecute() {
loadStarted();
String filepath = paths.PATH_LOCAL_HYDRANTS;
File hydrantsPath = new File(filepath);
hydrantsFiles = hydrantsPath.listFiles(new FilenameFilter() {
@Override public boolean accept(File dir, String filename) {
return filename.toLowerCase(Locale.ENGLISH).contains(
Constants.FILETYPE_CSV);
}
});
int lineCount = 0;
if (hydrantsFiles == null || hydrantsFiles.length == 0) {
if (!preferences.isFirstStartUp()) {
// TODO notify user
}
Log.e(TAG, "Missing hydrants");
if (moduleCallback != null) {
moduleCallback.doneLoadingModule(toString());
}
this.cancel(false);
} else {
for (File file : hydrantsFiles) {
// store linecount for visual progress update
lineCount += Files.lineCount(file);
}
}
}
// The code to be executed in a background thread.
@Override protected List<HydrantHolder> doInBackground(Void... args) {
List<HydrantHolder> all_hydrants = new ArrayList<HydrantHolder>();
// Directory path here
int totalLineProgress = 0;
// // required
int indexLatitude = modulePreferences.indexLatitude();
int indexLongitude = modulePreferences.indexLongitude();
// optional
int indexAddress = modulePreferences.indexAddress();
int indexAddressNumber = modulePreferences.indexAddressNumber();
int indexAddressRemark = modulePreferences.indexAddressRemark();
int indexRemark = modulePreferences.indexRemark();
// decimals
int latitude_decimal = modulePreferences.latitude_decimal();
int longitude_decimal = modulePreferences.longitude_decimal();
if (indexLatitude <= 0 || indexLongitude <= 0)
return all_hydrants;
for (File file : hydrantsFiles) {
BufferedReader in = null;
try {
String hydrantspath = paths.PATH_LOCAL_HYDRANTS;
File hydrantsPath = new File(hydrantspath);
// Read File Line By Line
in = new BufferedReader(new InputStreamReader(
new FileInputStream(file), "windows-1252"));
String strLine;
while ((strLine = in.readLine()) != null) {
totalLineProgress++;
String[] hydrantParts = strLine.split(";", -1);
String errors = "";
final String hydrantType = file.getName().replace(
Constants.FILETYPE_CSV, "");
File[] iconFiles = hydrantsPath
.listFiles(new FilenameFilter() {
@Override public boolean accept(File dir,
String filename) {
if (filename.contains(hydrantType)
&& filename
.contains(Constants.FILETYPE_PNG)) {
return true;
}
return false;
}
});
HydrantHolder.Builder hb = new HydrantHolder.Builder();
if (hydrantParts.length >= 5) {
hb.setHydrantType(hydrantType);
if (iconFiles.length != 0) {
hb.setIconPath(hydrantspath
+ File.separatorChar
+ iconFiles[0].getName());
}
hb.setLatitude(hydrantParts[indexLatitude],
latitude_decimal);
hb.setLongitude(hydrantParts[indexLongitude],
longitude_decimal);
if (indexAddress > 0)
hb.setAddress(hydrantParts[indexAddress]);
if (indexAddressNumber > 0)
hb.setAddressNumber(hydrantParts[indexAddressNumber]);
if (indexAddressRemark > 0)
hb.setAddressRemark(hydrantParts[indexAddressRemark]);
if (indexRemark > 0)
hb.setRemark(hydrantParts[indexRemark]);
if (hb.getErrors().isEmpty()) {
HydrantHolder hydrant = hb.build();
all_hydrants.add(hydrant);
} else {
// TODO write error file to Dropbox if possible,
// otherwise locally
Log.d(TAG, errors);
}
} else {
errors = "Missing information";
}
publishProgress(totalLineProgress);
}
} catch (InvalidPathException e) {
Log.e(TAG, e.getMessage(), e);
} catch (IOException e) {
Log.e(TAG, e.getMessage(), e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
}
}
Log.d(TAG, "" + all_hydrants.size());
return all_hydrants;
}
// Update the progress
@Override protected void onProgressUpdate(Integer... values) {
// set the current progress of the progress dialog
// if (progressDialog != null && values != null && values.length >
// 0) {
// progressDialog.setProgress(values[0]);
// }
}
@Override protected void onPostExecute(List<HydrantHolder> hydrants) {
// Device.releaseOrientation((Activity) context);
Log.d(TAG, "Saved " + hydrants.size() + " hydrants");
mHydrants = hydrants;
new PrepareMarkerOptionsTask(hydrants).execute();
super.onPostExecute(hydrants);
}
}
private class PrepareMarkerOptionsTask extends
AsyncTask<Void, Integer, Map<HydrantType, List<MarkerOptions>>> {
// Before running code in separate thread
List<HydrantHolder> mHydrants;
public PrepareMarkerOptionsTask(List<HydrantHolder> hydrants) {
this.mHydrants = hydrants;
mMarkers = new HashMap<HydrantsModule.HydrantType, List<MarkerOptions>>();
}
@Override protected void onPreExecute() {
}
// The code to be executed in a background thread.
@Override protected Map<HydrantType, List<MarkerOptions>> doInBackground(
Void... arg) {
for (HydrantHolder hydrant : mHydrants) {
final String hydrant_type = hydrant.getHydrantType();
final String hydrant_icon_path = hydrant.getIconPath();
double latitude = hydrant.getLatitude();
double longitude = hydrant.getLongitude();
final LatLng position = new LatLng(latitude, longitude);
final String address = hydrant.getAddress();
final String addressNumber = hydrant.getAddressNumber();
final String addressremark = hydrant.getAddressRemark();
final String remark = hydrant.getRemark();
// Log.d(TAG, hydrant.toString());
BitmapDescriptor icon = BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_RED);
if (!hydrant_icon_path.isEmpty()) {
File iconfile = new File(hydrant_icon_path);
if (iconfile.exists()) {
BitmapDescriptor loaded_icon = BitmapDescriptorFactory
.fromPath(hydrant_icon_path);
if (loaded_icon != null) {
icon = loaded_icon;
} else {
Log.e(TAG, "loaded_icon was null");
}
} else {
Log.e(TAG, "iconfile did not exist: "
+ hydrant_icon_path);
}
} else {
Log.e(TAG, "iconpath was empty on hydrant type: "
+ hydrant_type);
}
StringBuffer snippet = new StringBuffer();
if (!address.isEmpty())
snippet.append("\n" + address + " " + addressNumber);
if (addressremark.isEmpty())
snippet.append("\n" + addressremark);
if (!remark.isEmpty())
snippet.append("\n" + remark);
addHydrantMarker(
hydrant_type,
new MarkerOptions().position(position)
.title(hydrant_type)
.snippet(snippet.toString()).icon(icon));
// publishProgress(markers.size());
}
return mMarkers;
}
// Update the progress
@Override protected void onProgressUpdate(Integer... values) {
}
@Override protected void onCancelled() {
}
// after executing the code in the thread
@Override protected void onPostExecute(
Map<HydrantType, List<MarkerOptions>> markers) {
Log.d(TAG, "Prepared " + markers.size() + " hydrantMarkerOptions");
mMarkers = markers;
loadEnded();
super.onPostExecute(markers);
}
}
public Set<HydrantType> getHydrantTypes() {
return new HashSet<HydrantType>(mMarkers.keySet());
}
private void addHydrantMarker(String typeName, MarkerOptions marker) {
HydrantType type = new HydrantType(typeName, marker.getIcon());
if (mMarkers.containsKey(type)) {
List<MarkerOptions> markers = mMarkers.get(type);
markers.add(marker);
} else {
List<MarkerOptions> markers = new ArrayList<MarkerOptions>();
markers.add(marker);
mMarkers.put(type, markers);
enableHydrantType(type);
}
}
public class HydrantType {
private final String name;
private final BitmapDescriptor icon;
public HydrantType(String name, BitmapDescriptor icon) {
this.name = name;
this.icon = icon;
}
public String getName() {
return name;
}
public BitmapDescriptor getIcon() {
return icon;
}
@Override public int hashCode() {
return name.hashCode();
}
@Override public boolean equals(Object o) {
if (o instanceof HydrantType) {
if (((HydrantType) o).name.equals(name)) {
return true;
}
}
return false;
}
}
根据评论,我添加了更多文本和代码。
是的,我一次添加了所有的 MarkerOptions。虽然,因为我在添加所有标记之前放大了 GoogleMaps-extensions(第一个链接)只花费 CPU 能力将可见标记添加到 map 。如果用户平移 map 或缩小 map ,则会自动添加更多可见标记。
要使 map 延迟加载标记:
@Override
public void onMapReady(GoogleMap map) {
Log.i(TAG, "onMapReady");
if (map != null) {
map.setClustering(new ClusteringSettings().enabled(false)
.addMarkersDynamically(true));
}
}
还有我用来添加消防栓的代码(现在这在很多情况下过于复杂,但如果你仔细阅读,它只是简单地缩放到一个地址并在缩放完成后才添加所有消防栓):
public void addHydrantsNearAddress(final AddressHolder addressHolder,
final boolean zoomToAddress) {
final GoogleMap map = googlemaps.getMap();
final OnCameraChangeListener addHydrantsAfterZoom = new OnCameraChangeListener() {
@Override public void onCameraChange(CameraPosition cameraPosition) {
Log.d(TAG, cameraPosition.target.toString());
Log.d(TAG, addressHolder.position.toString());
final GoogleMap map = googlemaps.getMap();
// if (Location.distanceBetween(cameraPosition.tar,
// startLongitude, endLatitude, endLongitude, results)) {
new Handler().postDelayed(new Runnable() {
@Override public void run() {
addAllHydrants();
}
}, 500);
map.setOnCameraChangeListener(null); // unregister
// }
}
};
if (map == null) {
// wait for the map to be ready before adding hydrants
googlemaps.setGoogleMapsCallback(new GoogleMapsCallback() {
@Override public void mapReady(GoogleMap map) {
if (zoomToAddress) {
map.setOnCameraChangeListener(addHydrantsAfterZoom);
googlemaps.zoomTo(addressHolder);
} else {
addAllHydrants();
}
googlemaps.setGoogleMapsCallback(null); // unregister
}
});
} else {
if (zoomToAddress) {
// only setOnCameraChangeListener if cammera needs to move
LatLng cammeraPos = map.getCameraPosition().target;
LatLng addressPos = addressHolder.position;
float[] results = new float[1];
Location.distanceBetween(cammeraPos.latitude,
cammeraPos.longitude, addressPos.latitude,
addressPos.longitude, results);
// Log.d(TAG, "distance " + results[0]);
if (results[0] > 1) {
map.setOnCameraChangeListener(addHydrantsAfterZoom);
googlemaps.zoomTo(addressHolder);
} else {
googlemaps.zoomTo(addressHolder);
addAllHydrants();
}
}
}
}
为了举例,这个比需要的更复杂的另一个原因是我让用户在消防栓类型之间进行过滤。尽管如此,希望你能看到这个想法。
addAllHydrants() 听起来很简单,迭代 MarkerOptions 并添加它们:
public void addAllHydrants() {
enableAllHydrants();
GoogleMap map = googlemaps.getMap();
map.setTrafficEnabled(modulePreferences.showTraffic());
map.setMapType(modulePreferences.getMapType());
addHydrants(map);
}
private void addHydrants(GoogleMap map) {
Log.d(TAG, "addHydrants");
if (mHydrants == null || mHydrants.isEmpty()) {
Toast.makeText(context,
context.getString(R.string.missing_hydrants),
Toast.LENGTH_LONG).show();
return;
} else {
for (MarkerOptions marker : getEnabledHydrantMarkers()) {
map.addMarker(marker);
}
}
}
我真的认为,如果您尝试自己计算、获取和添加可见标记,而不仅仅是自己计算、获取和添加可见标记,那会使事情变得过于复杂。
回答关于onMapReady回调的问题
如果您使用 XML 添加 map ,那么我怀疑您可能不需要添加回调。尝试直接在 Activity 的 onCreate() 中使用 map 。只要对 getExtendedMap() 的调用不为空,就应该没问题。
我正在代码中创建 fragment ,以便能够将 Controller 代码与 map 放在一起。因此我的 SupportMapFragment 看起来像这样:
public class GoogleMapFragment extends SupportMapFragment {
private OnGoogleMapFragmentListener mCallback;
public GoogleMapFragment() {
mCallback = null;
}
public static interface OnGoogleMapFragmentListener {
void onMapReady(GoogleMap map);
}
@Override public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallback = (OnGoogleMapFragmentListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(getActivity().getClass().getName()
+ " must implement OnGoogleMapFragmentListener");
}
}
@Override public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
if (mCallback != null) {
mCallback.onMapReady(getExtendedMap());
}
return view;
}
}
关于android - 循环遍历 ArrayList 时在后台添加标记,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25517170/
当我使用Bundler时,是否需要在我的Gemfile中将其列为依赖项?毕竟,我的代码中有些地方需要它。例如,当我进行Bundler设置时:require"bundler/setup" 最佳答案 没有。您可以尝试,但首先您必须用鞋带将自己抬离地面。 关于ruby-我需要将Bundler本身添加到Gemfile中吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/4758609/
我脑子里浮现出一些关于一种新编程语言的想法,所以我想我会尝试实现它。一位friend建议我尝试使用Treetop(Rubygem)来创建一个解析器。Treetop的文档很少,我以前从未做过这种事情。我的解析器表现得好像有一个无限循环,但没有堆栈跟踪;事实证明很难追踪到。有人可以指出入门级解析/AST指南的方向吗?我真的需要一些列出规则、常见用法等的东西来使用像Treetop这样的工具。我的语法分析器在GitHub上,以防有人希望帮助我改进它。class{initialize=lambda(name){receiver.name=name}greet=lambda{IO.puts("He
我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代
我有一个ModularSinatra应用程序,我正在尝试将Bootstrap添加到应用程序中。get'/bootstrap/application.css'doless:"bootstrap/bootstrap"end我在views/bootstrap中有所有less文件,包括bootstrap.less。我收到这个错误:Less::ParseErrorat/bootstrap/application.css'reset.less'wasn'tfound.Bootstrap.less的第一行是://CSSReset@import"reset.less";我尝试了所有不同的路径格式,但它
我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
当谈到运行时自省(introspection)和动态代码生成时,我认为ruby没有任何竞争对手,可能除了一些lisp方言。前几天,我正在做一些代码练习来探索ruby的动态功能,我开始想知道如何向现有对象添加方法。以下是我能想到的3种方法:obj=Object.new#addamethoddirectlydefobj.new_method...end#addamethodindirectlywiththesingletonclassclass这只是冰山一角,因为我还没有探索instance_eval、module_eval和define_method的各种组合。是否有在线/离线资
我注意到类定义,如果我打开classMyClass,并在不覆盖的情况下添加一些东西我仍然得到了之前定义的原始方法。添加的新语句扩充了现有语句。但是对于方法定义,我仍然想要与类定义相同的行为,但是当我打开defmy_method时似乎,def中的现有语句和end被覆盖了,我需要重写一遍。那么有什么方法可以使方法定义的行为与定义相同,类似于super,但不一定是子类? 最佳答案 我想您正在寻找alias_method:classAalias_method:old_func,:funcdeffuncold_func#similartoca
我有带有Logo图像的公司模型has_attached_file:logo我用他们的Logo创建了许多公司。现在,我需要添加新样式has_attached_file:logo,:styles=>{:small=>"30x15>",:medium=>"155x85>"}我是否应该重新上传所有旧数据以重新生成新样式?我不这么认为……或者有什么rake任务可以重新生成样式吗? 最佳答案 参见Thumbnail-Generation.如果rake任务不适合你,你应该能够在控制台中使用一个片段来调用重新处理!关于相关公司
我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_