城市选择器

今天我们一起实现一个城市选择器。O.O

代码下载:
城市选择器 - 下载频道 - CSDN.NET
http://download.csdn.net/detail/baidu_31093133/9675482

效果图预览

主要包含以下内容:

1、自动定位所在城市
2、热门城市列表展示
3、所有城市列表的展示
4、输入城市名或者城市拼音搜索对应城市
5、右侧的slidebar城市列表导航栏

请大家先下载Demo然后再一边看demo一边看博客。因为博客里很多代码因为比较简单就不贴了。

首先我们先搭建基本的UI:

分析效果图,我们需要一个顶部title view,一个搜索框,一个定位功能的view,一个展示热门城市的view,一个侧边栏view和一个listview。

###顶部title View:

这里有一些需要注意的地方:
我们在新建工程的时候,android studio会自动生成一个style作为我们的主题:

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"><!-- Customize your theme here. --><item name="colorPrimary">@color/colorPrimary</item><item name="colorPrimaryDark">@color/colorPrimaryDark</item><item name="colorAccent">@color/colorAccent</item>
</style>    android:theme="@style/AppTheme"

这个默认的主题是带有actionbar的,如果我们要去掉这个actionbar,首先需要把DarkActionBar改为NoActionBar,因为使用AppCompatActivity的时候,Activity必须使用Theme.AppCompat主题及其子主题,所以我们的自定义的HD_NoActionBar样式必须继承这个主题:

 <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"><!-- Customize your theme here. --><item name="colorPrimary">@color/colorPrimary</item><item name="colorPrimaryDark">@color/colorPrimaryDark</item><item name="colorAccent">@color/colorAccent</item>
</style>
<style name="HD_NoActionBar" parent="AppTheme"><item name="android:windowNoTitle">true</item><item name="android:windowActionBar">false</item>
</style>

然后引用这个style:

android:theme="@style/AppTheme.NoActionBar"

接下来写我们的头布局 title_view.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="?attr/actionBarSize"android:background="@color/light_blue"><ImageViewandroid:id="@+id/back"style="@style/Widget.AppCompat.ActionButton"android:layout_width="wrap_content"android:layout_height="match_parent"android:paddingLeft="16dp"android:paddingRight="16dp"android:scaleType="center"android:src="@mipmap/ic_back"tools:ignore="ContentDescription" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:text="@string/select_city"android:textSize="20sp"android:textColor="@color/white" />
</RelativeLayout><Viewandroid:layout_width="match_parent"android:layout_height="1dp"android:background="@color/white"/>

布局返回按钮用一个ImageView,title用一个Textview。

然后在我们的主布局里使用标签引入头布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/light_blue"
android:orientation="vertical"><include layout="@layout/title_view" />

现在的效果是这样的:

搜索框布局 search view

search_view/xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="36dp"
android:layout_marginBottom="8dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="8dp"
android:gravity="center_vertical"
android:background="@drawable/search_box_bg"><ImageViewandroid:layout_width="wrap_content"android:layout_height="match_parent"android:paddingLeft="8dp"android:paddingRight="8dp"android:src="@mipmap/ic_search"android:scaleType="center"tools:ignore="ContentDescription" /><EditTextandroid:id="@+id/et_search"android:layout_weight="1"android:layout_width="0dp"android:layout_height="match_parent"android:background="@null"android:gravity="center_vertical"android:hint="@string/hint_search_box"android:textColorHint="@color/deep_blue"android:inputType="text"android:singleLine="true"android:textColor="@color/deep_blue"android:textSize="14sp"tools:ignore="RtlHardcoded" /><ImageViewandroid:id="@+id/iv_search_clear"android:layout_width="wrap_content"android:layout_height="match_parent"android:paddingLeft="8dp"android:paddingRight="8dp"android:src="@mipmap/ic_search_clear"android:visibility="gone"tools:ignore="ContentDescription" />

然后在主布局里引入这个布局:

<include layout="@layout/search_view"/>

搜索框的布局也非常简单,就不说明了。

现在的效果:

###城市列表

接下来的定位城市、热门城市、以及所有城市的列表我们使用一个Listview搞定,让Listview加载三种不同的布局来展示。

定位城市和所有城市列表好说,这个热门城市的UI该怎么做呢?我们准备使用gridview来做,在listview里嵌套gridview会遇到gridview只能显示一行的问题,我们先重现这个问题,然后再分析怎么解决。

listview需要一个adapter适配器,adapter需要一个数据源,我们的数据源存放在一个db数据库里,所以我们要构建一个数据库操作类,从数据库中取出这些城市然后展示出来。这一段的代码比较多,前方高能预警(__)

我们把要做的事情按步骤划分:

1、导入数据库文件
2、构建City对象,用户存储城市信息
3、创建DBManager用来操作数据库,将查询到的数据传递给adapter
4、编写定位城市、热门城市、所有城市三种不同的item布局
5、编写adapter,在adapter里加载三种item布局
6、编写gridview热门城市的item布局
7、实现gridview的adapter

####1、建立assets文件,并把db文件放在assets目录下:

####2、City对象

City.java:

    public class City {private String name;private String pinyin;public City() {}public City(String name, String pinyin) {this.name = name;this.pinyin = pinyin;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPinyin() {return pinyin;}public void setPinyin(String pinyin) {this.pinyin = pinyin;}
}

####数据库操作类:

DBManager.java:

  public class DBManager {private static final String ASSETS_NAME = "china_cities.db";private static final String DB_NAME = "china_cities.db";private static final String TABLE_NAME = "city";private static final String NAME = "name";private static final String PINYIN = "pinyin";private static final int BUFFER_SIZE = 1024;private String DB_PATH;private Context mContext;//初始化public DBManager(Context context) {this.mContext = context;DB_PATH = File.separator + "data"+ Environment.getDataDirectory().getAbsolutePath() + File.separator+ context.getPackageName() + File.separator + "databases" + File.separator;}//保存数据库到本地@SuppressWarnings("ResultOfMethodCallIgnored")public void copyDBFile(){File dir = new File(DB_PATH);if (!dir.exists()){dir.mkdirs();}File dbFile = new File(DB_PATH + DB_NAME);if (!dbFile.exists()){InputStream is;OutputStream os;try {is = mContext.getResources().getAssets().open(ASSETS_NAME);os = new FileOutputStream(dbFile);byte[] buffer = new byte[BUFFER_SIZE];int length;while ((length = is.read(buffer, 0, buffer.length)) > 0){os.write(buffer, 0, length);}os.flush();os.close();is.close();} catch (IOException e) {e.printStackTrace();}}}/*** 读取所有城市* @return*/public List<City> getAllCities(){SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DB_PATH + DB_NAME, null);Cursor cursor = db.rawQuery("select * from " + TABLE_NAME, null);List<City> result = new ArrayList<>();City city;while (cursor.moveToNext()){String name = cursor.getString(cursor.getColumnIndex(NAME));String pinyin = cursor.getString(cursor.getColumnIndex(PINYIN));city = new City(name, pinyin);result.add(city);}cursor.close();db.close();Collections.sort(result, new CityComparator());return result;}/*** 通过名字或者拼音搜索* @param keyword* @return*/public List<City> searchCity(final String keyword){SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DB_PATH + DB_NAME, null);Cursor cursor = db.rawQuery("select * from " + TABLE_NAME +" where name like \"%" + keyword+ "%\" or pinyin like \"%" + keyword + "%\"", null);List<City> result = new ArrayList<>();City city;while (cursor.moveToNext()){String name = cursor.getString(cursor.getColumnIndex(NAME));String pinyin = cursor.getString(cursor.getColumnIndex(PINYIN));city = new City(name, pinyin);result.add(city);}cursor.close();db.close();Collections.sort(result, new CityComparator());return result;}/*** a-z排序*/private class CityComparator implements Comparator<City> {@Overridepublic int compare(City lhs, City rhs) {String a = lhs.getPinyin().substring(0, 1);String b = rhs.getPinyin().substring(0, 1);return a.compareTo(b);}}
}

这个类使用SQLiteDatabase来管理数据库,同事写了一个排序类CityComparator用来对城市按照首字母进行排序

###定位城市的布局:
view_locate_city.xml:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:paddingBottom="8dp"tools:ignore="RtlHardcoded"><TextViewstyle="@style/LetterIndexTextViewStyle"android:text="@string/located_city"/><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="16dp"android:background="@color/content_bg"><LinearLayoutandroid:id="@+id/layout_locate"android:layout_width="wrap_content"android:layout_height="40dp"android:minWidth="96dp"android:paddingLeft="8dp"android:paddingRight="8dp"android:gravity="center"android:clickable="true"android:background="@drawable/overlay_bg"><ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:src="@mipmap/ic_locate"tools:ignore="ContentDescription" /><TextViewandroid:id="@+id/tv_located_city"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="8dp"android:text="@string/locating"android:textSize="16sp"android:textColor="@color/white"/></LinearLayout></LinearLayout>
</LinearLayout>

效果图

然后是所有城市的布局:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"tools:ignore="RtlHardcoded"><TextViewandroid:id="@+id/tv_item_city_listview_letter"style="@style/LetterIndexTextViewStyle"android:textSize="18sp"android:clickable="false"/><TextViewandroid:id="@+id/tv_item_city_listview_name"android:layout_width="match_parent"android:layout_height="48dp"android:paddingLeft="16dp"android:paddingRight="@dimen/side_letter_bar_width"android:background="?android:attr/selectableItemBackground"android:clickable="true"android:gravity="center_vertical"android:textSize="@dimen/city_text_size"android:textColor="@color/light_blue"/><Viewandroid:layout_width="match_parent"android:layout_height="1px"android:layout_marginLeft="16dp"android:layout_marginRight="@dimen/side_letter_bar_width"android:background="@color/divider"/>
</LinearLayout>

使用两个TextView一个用来显示城市的首字母,一个用来显示城市名字

上面的布局都很简单,接下来就是热门城市了:

如果我们使用gridview不做任何处理的话,最终效果是这样的:

不止在listview,gridview在其它任何可以滚动的view里都会出现这个问题,解决这个问题我们有固定的方案,那就是自定义一个gridview然后重写onMeasure方法,在onMeasure方法里,让gridview测量子view的高度,并全部显示出来。

代码其实非常简单:

    public class WrapHeightGridView extends GridView {public WrapHeightGridView(Context context) {this(context, null);}public WrapHeightGridView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public WrapHeightGridView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}//如果把GridView放到一个垂直方向滚动的布局中,设置其高度属性为 wrap_content ,// 则该GridView的高度只有一行内容,其他内容通过滚动来显示。// 如果你想让该GridView的高度为所有行内容所占用的实际高度,则可以通过覆写GridView的 onMeasure 函数来修改布局参数@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int heightSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);super.onMeasure(widthMeasureSpec, heightSpec);}
}

这里重点理解makeMeasureSpec这个方法,public static int makeMeasureSpec(int size, int mode)
这个是由我们给出的尺寸大小和模式生成一个包含这两个信息的int变量。这个int值有32位,其中高2位表示模式,低30位表示值。我们把Integer.MAX_VALUE >> 2右移两位然后和MeasureSpec.AT_MOST合成一个新的int值。Integer.MAX_VALUE是INT类型的最大值是0xFFFFFFFF,所以这个值右移两位就表示新的合成的int值得低30位都是1。也就是说我们取最大值作为控件高度的最大值。

这样就可以了,效果:

接下来看一下adapter是怎么实现的:

CityListAdapter.java:


public class CityListAdapter extends BaseAdapter{private static final int VIEW_TYPE_COUNT = 3;private Context mContext;private LayoutInflater inflater;private List<City> mCities;private HashMap<String, Integer> letterIndexes;private String[] sections;private OnCityClickListener onCityClickListener;private int locateState = LocateState.LOCATING;private String locatedCity;public CityListAdapter(Context mContext, List<City> mCities) {this.mContext = mContext;this.mCities = mCities;this.inflater = LayoutInflater.from(mContext);if (mCities == null){mCities = new ArrayList<>();}mCities.add(0, new City("定位", "0"));mCities.add(1, new City("热门", "1"));int size = mCities.size();letterIndexes = new HashMap<>();sections = new String[size];for (int index = 0; index < size; index++){//当前城市拼音首字母String currentLetter = PinyinUtils.getFirstLetter(mCities.get(index).getPinyin());//上个首字母,如果不存在设为""String previousLetter = index >= 1 ? PinyinUtils.getFirstLetter(mCities.get(index - 1).getPinyin()) : "";if (!TextUtils.equals(currentLetter, previousLetter)){letterIndexes.put(currentLetter, index);sections[index] = currentLetter;}}}/*** 更新定位状态* @param state*/public void updateLocateState(int state, String city){this.locateState = state;this.locatedCity = city;notifyDataSetChanged();}/*** 获取字母索引的位置* @param letter* @return*/public int getLetterPosition(String letter){Integer integer = letterIndexes.get(letter);return integer == null ? -1 : integer;}@Overridepublic int getViewTypeCount() {return VIEW_TYPE_COUNT;}@Overridepublic int getItemViewType(int position) {return position < VIEW_TYPE_COUNT - 1 ? position : VIEW_TYPE_COUNT - 1;}@Overridepublic int getCount() {return mCities == null ? 0: mCities.size();}@Overridepublic City getItem(int position) {return mCities == null ? null : mCities.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(final int position, View view, ViewGroup parent) {CityViewHolder holder;int viewType = getItemViewType(position);switch (viewType){case 0:     //定位view = inflater.inflate(R.layout.view_locate_city, parent, false);ViewGroup container = (ViewGroup) view.findViewById(R.id.layout_locate);TextView state = (TextView) view.findViewById(R.id.tv_located_city);switch (locateState){case LocateState.LOCATING:state.setText(mContext.getString(R.string.locating));break;case LocateState.FAILED:state.setText(R.string.located_failed);break;case LocateState.SUCCESS:state.setText(locatedCity);break;}container.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (locateState == LocateState.FAILED){//重新定位if (onCityClickListener != null){onCityClickListener.onLocateClick();}}else if (locateState == LocateState.SUCCESS){//返回定位城市if (onCityClickListener != null){onCityClickListener.onCityClick(locatedCity);}}}});break;case 1:     //热门view = inflater.inflate(R.layout.view_hot_city, parent, false);WrapHeightGridView gridView = (WrapHeightGridView) view.findViewById(R.id.gridview_hot_city);final HotCityGridAdapter hotCityGridAdapter = new HotCityGridAdapter(mContext);gridView.setAdapter(hotCityGridAdapter);gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {if (onCityClickListener != null){onCityClickListener.onCityClick(hotCityGridAdapter.getItem(position));}}});break;case 2:     //所有if (view == null){view = inflater.inflate(R.layout.item_city_listview, parent, false);holder = new CityViewHolder();holder.letter = (TextView) view.findViewById(R.id.tv_item_city_listview_letter);holder.name = (TextView) view.findViewById(R.id.tv_item_city_listview_name);view.setTag(holder);}else{holder = (CityViewHolder) view.getTag();}if (position >= 1){final String city = mCities.get(position).getName();holder.name.setText(city);//如果当前的item的城市的首字母和上一个城市的首字母相同,就不显示首字母否则就显示。  //这样就可以实现让所有城市根据首字母分类的效果了。String currentLetter = PinyinUtils.getFirstLetter(mCities.get(position).getPinyin());String previousLetter = position >= 1 ? PinyinUtils.getFirstLetter(mCities.get(position - 1).getPinyin()) : "";if (!TextUtils.equals(currentLetter, previousLetter)){holder.letter.setVisibility(View.VISIBLE);holder.letter.setText(currentLetter);}else{holder.letter.setVisibility(View.GONE);}holder.name.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (onCityClickListener != null){onCityClickListener.onCityClick(city);}}});}break;}return view;}public static class CityViewHolder{TextView letter;TextView name;}public void setOnCityClickListener(OnCityClickListener listener){this.onCityClickListener = listener;}public interface OnCityClickListener{void onCityClick(String name);void onLocateClick();}
}

Listview加载不同的布局,请参考我的另一篇博客:
最主要的就是getItemViewType方法。

listview加载不同布局 - 秦时明月 - 博客频道 - CSDN.NET
http://blog.csdn.net/baidu_31093133/article/details/51804923

在adapter里使用LocateState类来标示不同的定位状态。代码比较多,但是都很好理解就不再解释了(其实是懒。。。)

###gridview热门城市的item布局
就是一个Textview而已
###gridview热门城市的adapter
请参考demo,就是一个简单的adapter

###然后是侧边导航栏的实现:

写一个自定义view,
SideBar.java

    public class SideBar extends View {private static final String[] b = {"定位", "热门", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};private int choose = -1;private Paint paint = new Paint();private boolean showBg = false;private OnLetterChangedListener onLetterChangedListener;private TextView overlay;public SideBar(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}public SideBar(Context context, AttributeSet attrs) {super(context, attrs);}public SideBar(Context context) {super(context);}/*** 设置悬浮的textview* @param overlay*/public void setOverlay(TextView overlay){this.overlay = overlay;}@SuppressWarnings("deprecation")@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (showBg) {canvas.drawColor(Color.TRANSPARENT);}int height = getHeight();int width = getWidth();int singleHeight = height / b.length;//单行字母高度for (int i = 0; i < b.length; i++) {paint.setTextSize(getResources().getDimension(R.dimen.side_letter_bar_letter_size));paint.setColor(getResources().getColor(R.color.deep_blue));paint.setAntiAlias(true);if (i == choose) {paint.setColor(getResources().getColor(R.color.gray_deep));
//                paint.setFakeBoldText(true);  //加粗}float xPos = width / 2 - paint.measureText(b[i]) / 2;float yPos = singleHeight * i + singleHeight;canvas.drawText(b[i], xPos, yPos, paint);paint.reset();}}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {final int action = event.getAction();final float y = event.getY();final int oldChoose = choose;final OnLetterChangedListener listener = onLetterChangedListener;final int c = (int) (y / getHeight() * b.length);//获取字母的indexswitch (action) {case MotionEvent.ACTION_DOWN:showBg = true;if (oldChoose != c && listener != null) {if (c >= 0 && c < b.length) {listener.onLetterChanged(b[c]);choose = c;invalidate();if (overlay != null){overlay.setVisibility(VISIBLE);overlay.setText(b[c]);}}}break;case MotionEvent.ACTION_MOVE:if (oldChoose != c && listener != null) {if (c >= 0 && c < b.length) {listener.onLetterChanged(b[c]);choose = c;invalidate();if (overlay != null){overlay.setVisibility(VISIBLE);overlay.setText(b[c]);}}}break;case MotionEvent.ACTION_UP:showBg = false;choose = -1;invalidate();if (overlay != null){overlay.setVisibility(GONE);}break;}return true;}@Overridepublic boolean onTouchEvent(MotionEvent event) {return super.onTouchEvent(event);}public void setOnLetterChangedListener(OnLetterChangedListener onLetterChangedListener) {this.onLetterChangedListener = onLetterChangedListener;}public interface OnLetterChangedListener {void onLetterChanged(String letter);}}

我们绘制了slidebar,并且重写了它的ontouch事件。当手指摁下和滑动的时候在布局中央显示当前城市的首字母,显示首字母的控件是一个Textview,这个Textview通过setOverlay方法传递进来。抬起的时候不显示。

现在的效果是这样:

UI总算实现的差不多了,接下来还有城市定位功能,搜索功能以及右侧导航功能的要实现。

###搜索功能:

主要就是给edittext设置一个TextChangedListener,让它根据输入去数据库中查找数据并将数据传递给adapter,然后通过notifyDataSetChanged方法来更新UI。

代码请参照demo

###右侧导航功能
当我们的手指在slidebar上滑动的时候会触发它的ontouch事件,然后通过回调将当前的字母传递回来,接着我们将点击的字母传递给mCityAdapter的getLetterPosition方法,来得到当前字母的位置,并通过mListview.setSelection(position);方法来改变listview的显示位置

代码参考demo

###定位功能

这个需要接入百度地图的sdk

这个大家根据百度地图开发者中心的手册一点点来就可以了。

传送门:

android-locsdk/guide/key - Wiki
http://lbsyun.baidu.com/index.php?title=android-locsdk/guide/key

谢谢大家。(__)

一步步实现一个城市选择器相关推荐

  1. android 仿携程选择城市,类似携程商旅的城市选择器 CityPicker

    软件介绍 CityPicker 这是一个类似于携程商旅类型的城市选择器 特性 1.无侵入性 2.使用方便 展示 使用方法 1.将工程中CityPicker中的Citypicker文件夹拖入你要使用的工 ...

  2. vue | 基于vue的城市选择器和搜索城市对应的小区

    城市选择器应该是比较常用的一个组件,用户可以去选择自己的城市,选择城市后返回,又根据自己选择的城市搜索小区. 功能展示 这是选择结果 这是选择城市 这是搜索小区 这是搜索小区接口,key为城市名字,i ...

  3. Flutter时间选择器(城市选择器,时间选择器,数量选择器)

    倒入第三方库: flutter_picker: 1.1.0 github:https://github.com/yangyxd/flutter_picker flutter_picker的作者提供了多 ...

  4. html表单选择城市,基于weui的城市选择器(city-picker)

    基于weui的城市选择器(city-picker) 前言 最近在用weui做一个移动端的项目,有个城市选择器的需求,但是weui原生并不支持,需要自定义实现,查了一些资料,需求完美实现,下面分享下实现 ...

  5. 小程序插件引入(地铁图/路线规划/地图选点/城市选择器)

    小程序插件官方地址–腾讯 初始化 这边使用的是腾讯家的插件,所以我们要按照他家的要求一步一步来. 首先我们要申请一个key值(申请key值是去腾讯地图开发平台去找) 然后在小程序里面添加插件 (这2步 ...

  6. vue @click 赋值_vue 手写一个时间选择器

    vue 手写一个时间选择器 最近研究了 DatePicker 的实现原理后做了一个 vue 的 DatePicker 组件,今天带大家一步一步实现 DatePicker 的 vue 组件. 原理 Da ...

  7. 一步步实现一个基本的缓存模块

    一步步实现一个基本的缓存模块 注意后续代码及改进见后后文及github,文章上的并没有更新. 1. 前言     2.  请求级别缓存     2.1 多线程     3.  进程级别缓存     3 ...

  8. php类同时调用两个参数,如何让一个类选择器同时调用两个class值_html/css_WEB-ITnose...

    姓名 性别 年龄 名族 张峰 男 19 汉族 李惠 女 18 回族 请教一下为什么表格th部分没有变色,只是td部分变了,如何让一个类选择器同时调用两个class值 回复讨论(解决方案) 如何让类选择 ...

  9. 改时区_太平洋奇葩小国:为了虚名改时区,全国仅一个城市,至今仍是部落

    过府冲州带你看世界 ​ 到国外去旅游,很多人会选择海岛,像是马尔大夫.普吉岛等这样的美丽海岛,能够吸引无数的中国游客去旅游,出国海岛旅游,也曾经一度十分火热:就在前不久,中国和太平洋的岛国开展了&qu ...

最新文章

  1. 【[CQOI2018]交错序列】
  2. Android点击Button实现功能的几种方法总结
  3. 利用solr的 DataImportHandler从mysql数据库建立索引
  4. [Python设计模式] 第1章 计算器——简单工厂模式
  5. MFC六大核心机制之四:永久保存(串行化)
  6. DJANGO学习过程中遇到的问题
  7. 如何使用TensorFlow对象检测API播放Quidditch
  8. (52)多路时钟复用FPGA如何约束一(片外时钟复用约束)
  9. 在Adobe Html5 Extension的使用Nodejs的问题
  10. LA 4287 有相图的强连通分量
  11. 搭建一个网站的价格以及步骤全面解读
  12. Unity官方文档(英文)
  13. 软件工程基础知识--系统测试
  14. 基于Android的计算器app设计
  15. img下方出现空隙的原因及解决办法
  16. fpga+ADS1256
  17. Outlook无法打开 .ost文件损坏,卸载重装也恢复不了
  18. 一些基本的Oracle命令
  19. 微信公众号和微信小程序进程名获取及配置
  20. Java仿QQ聊天系统Eclipse+MySql实现

热门文章

  1. acdsee单文件版_96层QLC 美光Crucial X8移动固态硬盘2TB版评测 - 企业资讯
  2. 利用Python自制一个批量图片水印添加器
  3. nrf52832学习笔记(3)设置发射功率
  4. VMware虚拟机黑群晖添多网卡教程
  5. ctfshow 反序列化篇(下)
  6. core java文献翻译_外文翻译--Java技术介绍(适用于毕业论文外文翻译+中英文对照).doc...
  7. CodeReview实践-Gerrit自动触发JenkinsCI
  8. Java GUI编程学习
  9. 通信达linux版,中标麒麟V6下wine完美运行通达信
  10. 有的歌声音大有的歌声音小_同样是mp3格式的歌,同一个耳机,在保证音量大小不变情况下,为什么有的歌声音大有的歌声音小?...