重要的事情在前面说

  • 本文介绍的方法是基于坐标动态计算该Item在RecyclerView中的行列值实现的,是我最初的设计思路,相对有局限性,后面借鉴RecyclerView的相关API进行了重新设计新的设计思路。

推荐阅读

  • 仿微信QQ显示手机相册

  • 将视频图片保存到系统相册

演示视频

  • 演示视频

前言

  • 显示相册在app中是一个比较常见的操作,大致的操作就是通过ContentProvider获取多媒体资源进行展示,我综合了一下QQ 的和微信的显示效果,实现了一下,仿微信QQ显示手机相册,在QQ的相册选择时是支持滑动选中的,即手指碰到哪个就选中哪张照片,正好公司的项目中用到了这个功能,在网上找了找没有很好的解决方案,所以通过自定义控件处理事件,这篇文章主要介绍这个功能的实现。

大体思路

  • 打算继承FramLayout实现,当然继承别的也可以,习惯继承FramLayout

  • 当手指竖向滑动时,RecyclerView处理事件,进行滑动。

  • 当手指水平滑动时,SlidingCheckLayout截断事件,计算滑动距离,通过计算映射出手指滑动的位置在RecyclerView中的position

对外封闭

  • 为了尽量对外封闭,提供相对简单的API,一些操作需要内部来处理来获取相关数据,比如从内部获取RecyclerView,item的宽度,高度,GridLayoutManager的列数等参数,使用者就不需要再进行设置,相对使用起来更加简单。

  • 获取RecyclerView

    private void ensureTarget() {if (mTargetRv != null)return;for (int i = 0; i < getChildCount(); i++) {View childAt = getChildAt(i);if (childAt instanceof RecyclerView) {mTargetRv = (RecyclerView) childAt;// 设置监听事件initRecyclerView();return;}}}// 设置监听事件,因为使用RecyclerView的addOnScrollListener方法,所以对外提供一个兼容innerOnScrollListener来给RecyclerView设置监听private void initRecyclerView() {mTargetRv.addOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrolled(RecyclerView recyclerView, int dx, int dy) {super.onScrolled(recyclerView, dx, dy);mTotalScrollY += dy;if (innerOnScrollListener != null) {innerOnScrollListener.onScrolled(recyclerView, dx, dy);}}@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);if (innerOnScrollListener != null) {innerOnScrollListener.onScrollStateChanged(recyclerView, newState);}}});}
  • 处理LayoutManager
    /*** 换LayoutManager需要调用* 获取itemCount,初始化item的高度和宽度*/public void ensureLayoutManager() {if (mTargetRv == null || itemSpanCount != -1)return;RecyclerView.LayoutManager lm = mTargetRv.getLayoutManager();if (lm == null)return;if (lm instanceof GridLayoutManager) {GridLayoutManager glm = (GridLayoutManager) lm;itemSpanCount = glm.getSpanCount();int size = (int) (getResources().getDisplayMetrics().widthPixels / (itemSpanCount * 1.0f));itemWidth = itemHeight = size;xTouchSlop = yTouchSlop = size * TOUCH_SLOP_RATE;} else {throw new IllegalStateException("only support grid layout manager now !");}}
  • 处理adapter
public void ensureAdapter() {if (mTargetRv == null || mDataCount != INVALID_PARAM)return;mAdapter = mTargetRv.getAdapter();if (mAdapter == null)return;mDataCount = mAdapter.getItemCount();mDataChangedObserver = new RecyclerView.AdapterDataObserver() {@Overridepublic void onChanged() {super.onChanged();ensureAdapter();}};mAdapter.registerAdapterDataObserver(mDataChangedObserver);}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();mAdapter.unregisterAdapterDataObserver(mDataChangedObserver);}

拦截事件

private boolean isReadyToIntercept() {return mTargetRv != null && mTargetRv.getAdapter() != null && itemSpanCount != INVALID_PARAM;
}@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {if (!isEnabled())return super.onInterceptTouchEvent(ev);ensureTarget();ensureLayoutManager();ensureAdapter();if (!isReadyToIntercept())return super.onInterceptTouchEvent(ev);int action = MotionEventCompat.getActionMasked(ev);switch (action) {case MotionEvent.ACTION_DOWN:// initmInitialDownX = ev.getX();mInitialDownY = ev.getY();break;case MotionEvent.ACTION_UP:// stopisBeingSlide = false;break;case MotionEvent.ACTION_MOVE:// handle// 水平滑动超过阈值,垂直滑动没有超过阈值时拦截事件float xDiff = Math.abs(ev.getX() - mInitialDownX);float yDiff = Math.abs(ev.getY() - mInitialDownY);if (yDiff < xTouchSlop && xDiff > yTouchSlop) {isBeingSlide = true;isBeingSlide = true;initRow = generateRow(ev.getX());initColumn = generateColumn(ev.getY());            }break;}return isBeingSlide;
}

计算行列值和位置

  • 列值等于x轴移动的距离/每个item 的宽度

  • 行值等于(RecyclerView滑动的距离+手指滑动的距离-RecylerView距离顶部偏移)(一般不设置magin就是0,兼容多种情况)。

    private int generateRow(float x) {return (int) (x / itemWidth);}private int generateColumn(float y) {return (int) ((y + mTotalScrollY - offsetTop) / itemHeight);}/*** 计算位置,根据行列值计算,比如*     0 1 2(row)*         * 0   0 1 2* 1   3 4 5* 2   6 7 8* (col)* 7 = 1(row) + 3 * 2(col)* @param col 行* @param row 列* @return*/private int calculatePosition(int col, int row) {return row + itemSpanCount * col;}

触摸事件

  • 重点是up事件时重新初始化一些值

  • move事件时处理位置的移动

    @Overridepublic boolean onTouchEvent(MotionEvent ev) {int action = MotionEventCompat.getActionMasked(ev);switch (action) {case MotionEvent.ACTION_DOWN:break;case MotionEvent.ACTION_UP:// stopisBeingSlide = false;xSlidingDist = 0;ySlidingDist = 0;preTouchPos = INVALID_PARAM;preRow = INVALID_PARAM;preColumn = INVALID_PARAM;break;case MotionEvent.ACTION_MOVE:handleMoveEvent(ev);break;}return isBeingSlide;}
    /*** 处理滑动手势** @param ev move事件*/private void handleMoveEvent(MotionEvent ev) {xSlidingDist = ev.getX();ySlidingDist = ev.getY();mRow = generateRow(xSlidingDist);if (mRow >= itemSpanCount)return;mColumn = generateColumn(ySlidingDist);int pos = (mRow + 3 * mColumn);if (onSlidingCheckListener == null || pos == preTouchPos)return;preTouchPos = pos;{publishSlidingCheck(mColumn, mRow);}preColumn = mColumn;preRow = mRow;}/*** 公开滑动选中事件** @param col 行* @param row 列*/private void publishSlidingCheck(int col, int row) {int pos = calculatePosition(col, row);if (onSlidingCheckListener != null && pos < mDataCount) {onSlidingCheckListener.onSlidingCheck(pos);}}

进阶

  • 对角线滑动选中多个

  • 先横向后垂直滑动选中多个

  • 使用这些手势达到很快选中多个的目的,但是相对也失去了滑动到哪里选中哪里的灵活性,二者选一吧
  • 完整的move事件处理,支持选中多个,感觉还是滑倒哪里选到哪里比较好
    private void handleMoveEvent(MotionEvent ev) {xSlidingDist = ev.getX();ySlidingDist = ev.getY();mRow = generateRow(xSlidingDist);if (mRow >= itemSpanCount)return;mColumn = generateColumn(ySlidingDist);int pos = (mRow + 3 * mColumn);if (onSlidingCheckListener == null || pos == preTouchPos)return;preTouchPos = pos;// 处理垂直滑动事件,如果支持7型手势,垂直滑动变为选中多张照片if (preRow != INVALID_PARAM && preRow == mRow && isSupport7Gesture) {// 先右划固定后上下滑选择if (mRow > initRow) {// 7型手势向下滑动if (preColumn < mColumn)for (int row = initRow; row <= mRow; row++)for (int col = preColumn + 1; col <= mColumn; col++)publishSlidingCheck(col, row);// 7型手势向上划if (preColumn > mColumn)for (int row = initRow; row <= mRow; row++)for (int col = preColumn - 1; col >= mColumn; col--)publishSlidingCheck(col, row);}//先左滑后上下滑else {// 7型手势向下滑动if (preColumn < mColumn)for (int row = mRow; row <= initRow; row++)for (int col = preColumn + 1; col <= mColumn; col++)publishSlidingCheck(col, row);// 7型手势向上划if (preColumn > mColumn)for (int row = mRow; row <= initRow; row++)for (int col = preColumn - 1; col >= mColumn; col--)publishSlidingCheck(col, row);}}// 简单滑动事件else {publishSlidingCheck(mColumn, mRow);}preColumn = mColumn;preRow = mRow;}

使用

  • 在xml中使用
<?xml version="1.0" encoding="utf-8"?>
<com.march.baselib.widget.SlidingCheckLayout
    xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/white"android:id="@+id/sliding"><android.support.v7.widget.RecyclerView
        android:id="@+id/rv_select_image"android:layout_width="match_parent"android:layout_height="match_parent"android:overScrollMode="never"app:spanCount="3"android:background="@color/white"android:clipChildren="false"android:clipToPadding="false"android:paddingBottom="50dp"tools:listitem="@layout/select_image_item_rv" />
</com.march.baselib.widget.SlidingCheckLayout>
  • 设置监听得到返回的pos进行处理
private SlidingCheckLayout mSlidingCheckLy;
mSlidingCheckLy.setOnSlidingCheckListener(new SlidingCheckLayout.OnSlidingCheckListener() {@Overridepublic void onSlidingCheck(int pos) {....adapter.notifyDataSetChanged();}});

源代码

package com.march.slidingcheck;import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;import com.march.baselib.helper.Logger;/*** Project  : SlidingCheck* Package  : com.march.slidingcheck* CreateAt : 16/9/6* Describe : 仿扣扣滑动选中照片** @author chendong*/
public class SlidingCheckLayout extends FrameLayout {private RecyclerView.Adapter mAdapter;private RecyclerView.AdapterDataObserver mDataChangedObserver;public SlidingCheckLayout(Context context) {this(context, null);}public SlidingCheckLayout(Context context, AttributeSet attrs) {super(context, attrs);}private static final float TOUCH_SLOP_RATE = 0.25f;// 初始化值private static final int INVALID_PARAM = -1;// 滑动选中监听private OnSlidingCheckListener onSlidingCheckListener;// 兼容滑动监听private RecyclerView.OnScrollListener innerOnScrollListener;private int offsetTop = 0;// rv在y轴滑动的距离private int mTotalScrollY = 0;// 横向的item数量private int itemSpanCount = INVALID_PARAM;// 数据量private int mDataCount = INVALID_PARAM;// 内部的rvprivate RecyclerView mTargetRv;// 是否支持7型手势操作private boolean isSupport7Gesture = false;// item宽度,默认屏幕宽度/span countprivate int itemWidth;// item高度,默认等于宽度,可以配置private int itemHeight;// 横轴滑动阈值,超过阈值表示触发横轴滑动private float xTouchSlop;// 纵轴滑动阈值,超过阈值表示触发纵轴滑动private float yTouchSlop;// down 事件初始值private float mInitialDownX;// down 事件初始值private float mInitialDownY;// 是否正在滑动private boolean isBeingSlide;// 行 值private int mColumn;// 列 值private int mRow;// 横向滑动距离private float xSlidingDist = 0;// 纵向滑动距离private float ySlidingDist = 0;// 上次触摸的位置private int preTouchPos = INVALID_PARAM;// 上次触摸的行数private int preColumn;// 上次触摸的列数private int preRow;// 初次down事件时触摸的行列值private int initRow;private int initColumn;private boolean isRowColumnEnable;public void setRowColumnEnable(boolean rowColumnEnable) {isRowColumnEnable = rowColumnEnable;}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {if (!isEnabled())return super.onInterceptTouchEvent(ev);ensureTarget();ensureLayoutManager();ensureAdapter();if (!isReadyToIntercept())return super.onInterceptTouchEvent(ev);int action = MotionEventCompat.getActionMasked(ev);switch (action) {case MotionEvent.ACTION_DOWN:// initmInitialDownX = ev.getX();mInitialDownY = ev.getY();break;case MotionEvent.ACTION_UP:// stopisBeingSlide = false;break;case MotionEvent.ACTION_MOVE:// handlefloat xDiff = Math.abs(ev.getX() - mInitialDownX);float yDiff = Math.abs(ev.getY() - mInitialDownY);if (yDiff < xTouchSlop && xDiff > yTouchSlop) {isBeingSlide = true;initRow = generateRow(ev.getX());initColumn = generateColumn(ev.getY());}break;}return isBeingSlide;}//  计算获取列值private int generateRow(float x) {return (int) (x / itemWidth);}// 计算获取列值private int generateColumn(float y) {return (int) ((y + mTotalScrollY - offsetTop) / itemHeight);}private float generateX(float x) {return x;}private float generateY(float y) {return y + mTotalScrollY - offsetTop;}@Overridepublic boolean onTouchEvent(MotionEvent ev) {int action = MotionEventCompat.getActionMasked(ev);switch (action) {case MotionEvent.ACTION_DOWN:break;case MotionEvent.ACTION_UP:// re initisBeingSlide = false;xSlidingDist = 0;ySlidingDist = 0;preTouchPos = INVALID_PARAM;preRow = INVALID_PARAM;preColumn = INVALID_PARAM;break;case MotionEvent.ACTION_MOVE:handleMoveEvent(ev);break;}return isBeingSlide;}/*** 处理滑动手势** @param ev move事件*/private void handleMoveEvent(MotionEvent ev) {publishSlidingCheck(ev);xSlidingDist = ev.getX();ySlidingDist = ev.getY();if (!isRowColumnEnable)return;mRow = generateRow(xSlidingDist);if (mRow >= itemSpanCount)return;mColumn = generateColumn(ySlidingDist);int pos = (mRow + 3 * mColumn);if (onSlidingCheckListener == null || pos == preTouchPos)return;preTouchPos = pos;// 处理垂直滑动事件,如果支持7型手势,垂直滑动变为选中多张照片if (preRow != INVALID_PARAM && preRow == mRow && isSupport7Gesture) {// 先右划固定后上下滑选择if (mRow > initRow) {// 7型手势向下滑动if (preColumn < mColumn)for (int row = initRow; row <= mRow; row++)for (int col = preColumn + 1; col <= mColumn; col++)publishSlidingCheck(col, row);// 7型手势向上划if (preColumn > mColumn)for (int row = initRow; row <= mRow; row++)for (int col = preColumn - 1; col >= mColumn; col--)publishSlidingCheck(col, row);}//先左滑后上下滑else {// 7型手势向下滑动if (preColumn < mColumn)for (int row = mRow; row <= initRow; row++)for (int col = preColumn + 1; col <= mColumn; col++)publishSlidingCheck(col, row);// 7型手势向上划if (preColumn > mColumn)for (int row = mRow; row <= initRow; row++)for (int col = preColumn - 1; col >= mColumn; col--)publishSlidingCheck(col, row);}}// 简单滑动事件else {publishSlidingCheck(mColumn, mRow);}preColumn = mColumn;preRow = mRow;}/*** 计算位置,根据行列值计算,比如* 0 1 2(row)* <p/>* 0   0 1 2* 1   3 4 5* 2   6 7 8* (col)* 7 = 1(row) + 3 * 2(col)** @param col 行* @param row 列* @return*/private int calculatePosition(int col, int row) {return row + itemSpanCount * col;}/*** 公开滑动选中事件** @param col 行* @param row 列*/private void publishSlidingCheck(int col, int row) {int pos = calculatePosition(col, row);if (onSlidingCheckListener != null && pos < mDataCount) {onSlidingCheckListener.onSlidingCheck(pos);}}private int preViewPos;private void publishSlidingCheck(MotionEvent event) {float x = generateX(event.getX());float y = generateY(event.getY()) - mTotalScrollY;View childViewUnder = mTargetRv.findChildViewUnder(x, y);if (onSlidingCheckListener != null && childViewUnder != null) {int pos = getPos(childViewUnder);if (pos != INVALID_PARAM && preViewPos != pos) {onSlidingCheckListener.onSlidingCheck(pos, childViewUnder);preViewPos = pos;}}}private int tagKey;public void setTagKey(int tagKey) {this.tagKey = tagKey;}public void mark(View parentView, int pos) {parentView.setTag(tagKey, pos);}public int getPos(View parentView) {int pos = INVALID_PARAM;Object tag = parentView.getTag(tagKey);if (tag != null)pos = (int) tag;return pos;}/*** 是否可以开始拦截处理事件,当recyclerView数据完全ok之后开始** @return 是否可以开始拦截处理事件*/private boolean isReadyToIntercept() {return mTargetRv != null&& mTargetRv.getAdapter() != null&& itemSpanCount != INVALID_PARAM;}/*** 获取RecyclerView*/private void ensureTarget() {if (mTargetRv != null)return;for (int i = 0; i < getChildCount(); i++) {View childAt = getChildAt(i);if (childAt instanceof RecyclerView) {mTargetRv = (RecyclerView) childAt;initRecyclerView();return;}}}/*** 为RecyclerView设置监听事件*/private void initRecyclerView() {mTargetRv.addOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrolled(RecyclerView recyclerView, int dx, int dy) {super.onScrolled(recyclerView, dx, dy);mTotalScrollY += dy;if (innerOnScrollListener != null) {innerOnScrollListener.onScrolled(recyclerView, dx, dy);}}@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);if (innerOnScrollListener != null) {innerOnScrollListener.onScrollStateChanged(recyclerView, newState);}}});}/*** 监测是否配置adapter*/public void ensureAdapter() {if (mTargetRv == null || mDataCount != INVALID_PARAM)return;mAdapter = mTargetRv.getAdapter();if (mAdapter == null)return;mDataCount = mAdapter.getItemCount();mDataChangedObserver = new RecyclerView.AdapterDataObserver() {@Overridepublic void onChanged() {super.onChanged();ensureAdapter();}};mAdapter.registerAdapterDataObserver(mDataChangedObserver);}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();mAdapter.unregisterAdapterDataObserver(mDataChangedObserver);}/*** 换LayoutManager需要调用* 获取itemCount,初始化item的高度和宽度*/public void ensureLayoutManager() {if (mTargetRv == null || itemSpanCount != INVALID_PARAM)return;RecyclerView.LayoutManager lm = mTargetRv.getLayoutManager();if (lm == null)return;if (lm instanceof GridLayoutManager) {GridLayoutManager glm = (GridLayoutManager) lm;itemSpanCount = glm.getSpanCount();int size = (int) (getResources().getDisplayMetrics().widthPixels / (itemSpanCount * 1.0f));itemWidth = itemHeight = size;xTouchSlop = yTouchSlop = size * TOUCH_SLOP_RATE;} else {throw new IllegalStateException("only support grid layout manager now !");}}/*** 由于内部封闭了OnScrollListener,对外开放一个兼容方法** @param innerOnScrollListener OnScrollListener*/public void addOnScrollListener(RecyclerView.OnScrollListener innerOnScrollListener) {this.innerOnScrollListener = innerOnScrollListener;}/*** 设置item的高度,默认与宽度相同,可以自行设置。** @param itemHeight 高度*/public void setItemHeight(int itemHeight) {this.itemHeight = itemHeight;this.yTouchSlop = itemHeight * TOUCH_SLOP_RATE;}public void setOffsetTop(int offsetTop) {this.offsetTop = offsetTop;}public void setSupport7Gesture(boolean support7Gesture) {isSupport7Gesture = support7Gesture;}public void setOnSlidingCheckListener(OnSlidingCheckListener onSlidingCheckListener) {this.onSlidingCheckListener = onSlidingCheckListener;}
}

仿QQ相册RecyclerView滑动选中相关推荐

  1. AndroidRecyclerView仿QQ相册功能

    实例模仿新版QQ相册功能,RecyclerView实现相册选择,DiskLruCache实现图片缓存,ItemTouchHelper实现图片的拖拽排序,单线程轮播解决加载大量图片卡顿问题(参考:htt ...

  2. 仿QQ对话列表滑动删除与置顶的原理及实现

    接下来,我们将完成QQ聊天界面的ListView滑动效果,大家可能都用过ListView,知道ListView是上下滑动的,并不会产生左右滑动的效果,如果想让ListView变成左右滑动的效果,必须对 ...

  3. PyQt5实现侧面菜单栏-仿QQ设置--以及透明选中按钮效果(详细讲解)

    这是我们要仿制的样式,甚至你可以在这个界面中弄更漂亮的界面效果 源码和UI文件都会开放的 这是我们实现的效果,可以看到切换页面的同时按钮会有被选中的感觉,这是通过label去实现的 下面开始教程    ...

  4. android 仿qq相册功能,Android第四十九期 - 仿QQ空间上传功能+本地数据库存储

    最近在看撸撸代码,他自己创了一种,网上有三种,分别是OpenDroid,greenDao,sugar,Sqlite原生写法,感觉都差不多,个人使用最优的是greenDao,下面开始介绍:    1.O ...

  5. uniapp框架如何实现仿微信相册 | 图视频过滤、相册选择功能

    今天我们分享基于uniapp + vue实现仿微信相册实例,该插件完全还原了微信相册的功能 1: 相册选择 2: 图片,视频类型过滤 3: 自定义相册界面UI 技术实现 开发环境:HbuilderX ...

  6. uniapp框架如何实现仿微信相册插件 | 图视频编辑 + 压缩

    在上上篇文章中(),我们基于uniapp框架实现了仿微信相册中的拍照+录像功能.今天,就继续在uni-app中实现: 1: 图片编辑 2: 视频编辑 3: 文件压缩 技术实现 开发环境:Hbuilde ...

  7. JS 点击弹出图片/ 仿QQ商城点击左右滚动幻灯片/ 相册模块,点击弹出图片,并左右滚动幻灯片...

    1, 点击弹出图片 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://w ...

  8. android+qq底部界面,Android 高仿QQ 界面滑动效果

    Android高仿QQ界面滑动效果 点击或者滑动切换画面,用ViewPager实现, 首先是布局文件: android:layout_width="match_parent" an ...

  9. 仿腾讯QQ竖直滑动导航菜单

    菜单就像qq软件的分组,鼠标经过自动显示相应组的内容. 效果体验网址:http://keleyi.com/a/bjad/nf86w2dv.htm 以下是源代码: 1 <html> 2 &l ...

最新文章

  1. 3.5. Error Prompt
  2. 2019牛客暑期多校训练营(第七场)J A+B problem
  3. ABAP Method remote comparison
  4. ETL异构数据源Datax_datax-web安装部署_10
  5. 关于Java单例模式中懒汉式和饿汉式的两种类创建方法
  6. 系统设计和机器学习算法
  7. NUMA架构下的CPU拓扑
  8. 【基础数学】Jensen不等式
  9. Airsim动态 | ROS教程(AirSim ROS Tutorial)
  10. 链接:https://pan.baidu.com/s/1U-UlYoranUvpfj97XLrHvQ 提取码:ZMTD
  11. Python练习题(1)
  12. 如何进行CMDB数据运营?
  13. 【Python】Transformers加载BERT模型from_pretrained()问题解决
  14. 18 《社会性动物》 -豆瓣评分9.1
  15. AI_图像标注:图像标注简介
  16. Job中织梦标签的调用
  17. Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependen
  18. 微服务---统一网关Gateway
  19. 微信网页和app内h5页获取当前定位保持一致
  20. etcd v3的webui管理界面

热门文章

  1. 司创电梯发卡软件_防复制电梯IC卡系统上位机管理软件设计
  2. svn常用命令以及冲突解决
  3. linux网卡断流测试,RouterOS断流解决办法探讨
  4. ROS学习——Ubuntu 安装软件报错问题(关于进程锁)
  5. 图解:什么是红黑树?(下篇)
  6. 第一部分:简单句——第一章:简单句的核心——一、简单句的核心构成
  7. Java 第一个程序Hello
  8. C#之 十九 使用WinForm控件
  9. ArcGis配色心得
  10. Arcgis Engine10.2安装包和安装教程