原标题:Android简易天气App

前言

本打算是写一个贝塞尔曲线的demo,想了一下哪种场景可以直观的表现出贝塞尔曲线,想到天气预报中的那些24小时和未来几日天气变化正好适用。接着开始构思,开始是打算把数据写死的,然后想了想既然是模拟天气预报,为了真实一点,干脆就从网络获取吧,就找了个天气接口,一看接口还有好多其他的数据,干脆都用了吧。然后又想了一想,既然已经做成天气预报了,切换城市是必须要有的吧。最后就变成了一个简易的天气App。

先上效果图,上面和下面两部分是显示的一些基本数据,中间那个可滑动的未来15日天气就是一开始打算写的贝塞尔曲线部分。选择左上角的城市,会跳转到搜索界面,可以搜索想要查看城市的天气状况。

使用到的知识

网络请求:Retrofit + RxJava

网络数据解析:Gson

天气图标获取:Glide

主界面和搜索界面的消息传递:EventBus

天气变化曲线:自定义View

数据缓存:SharedPreference

视图绑定:ButterKnife

搜索编辑框:DataBinding

搜索结果列表:RecyclerView

准备工作

一共使用了3个接口,一个用来请求天气数据,一个用来请求天气类型的图标,一个用来搜索城市。请求天气的接口为http://t.weather.sojson.com/api/weather/city/city_code,接口及返回Json数据示例https://www.sojson.com/blog/305.html有说明,其中的city_code通过搜索城市的接口得到,使用的是和风天气的接口。返回数据中的天气类型用来请求天气类型图标,也是和风天气的接口。和风天气的官网上有说明。请求天气接口返回的数据是Json格式的,这就需要做Json的解析,我这里用的是Gson。使用Gson是需要与数据相对应的Bean类的,Android Studio中正好有一个插件叫做GsonFormat,可以自动生成对应的Bean类。选择File Settings/Plugins/MarketPlace,搜索GsonFormat,安装就可以了,我这个是已经安装过了。

在app的build.gradle中添加要使用到的依赖。直接都添加好了,已经将后续所有用到的都添加了好了。顺便添加compileOptions和dataBinding两段代码。

android {

...

compileOptions {

sourceCompatibility JavaVersion.VERSION_1_8

targetCompatibility JavaVersion.VERSION_1_8

}

dataBinding {

enabled = true

}

}

dependencies {

...

implementation 'com.android.support:recyclerview-v7:28.0.0'

implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'

implementation 'io.reactivex.rxjava2:rxjava:2.2.6'

implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'

implementation 'com.squareup.retrofit2:converter-gson:2.2.0'

implementation 'com.squareup.retrofit2:retrofit:2.2.0'

implementation 'com.jakewharton:butterknife:9.0.0'

implementation 'com.squareup.okhttp3:okhttp:3.10.0'

implementation 'org.greenrobot:eventbus:3.0.0'

annotationProcessor 'com.jakewharton:butterknife-compiler:9.0.0'

...

}

在project的build.gradle中添加

dependencies {

...

classpath 'com.jakewharton:butterknife-gradle-plugin:9.0.0'

classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

// NOTE:Do not place your application dependencies here; they belong

// in the individual module build.gradle files

}

天气数据网络请求

分为两块,一块是天气和图标的请求,使用RxJava的flatMap操作符将两者连在一起:首先请求天气数据,得到数据后使用flatMap操作符,取出数据中的天气类型进行第二次网络请求,最后在主线程中处理数据。第二块是搜索城市返回城市代码,一个简单的Retrofit + RxJava就可实现。

在https://www.sojson.com/blog/305.html中,有天气接口成功返回值的Json代码示例,去掉中间的注释,只要Json数据。因为使用的Gson,首先准备天气数据的Bean类。新建WeatherBean.java,将光标放到类的括号中,右键选择Generate,选择GsonFormat,把Json代码粘进去,ok,会在类中自动生成对应的代码,看一眼与数据是对应的。

写Retrofit中的service接口。新建WeatherService.java,类型为interface。添加getCall方法,GET请求,因为接口为http://t.weather.sojson.com/api/weather/city/+city_code。每次请求改变city_code即可,通过@Path注解实现。类型Observable< WeatherBean >,其中Observable是因为用了RxJava,WeatherBean就是前面生成的Bean类。

publicinterfaceWeatherService{

//每次请求city_code可变

@GET("{city_code}")

Observable getCall( @Path("city_code")String code);

}

在MainActivity中使用Retrofit,添加requestWeather(String cityId)方法。

@SuppressLint( "CheckResult")

privatevoidrequestWeather(String cityId){

bitmaps.clear;

Retrofit retrofit = newRetrofit.Builder

.baseUrl( "http://t.weather.sojson.com/api/weather/city/")

.addConverterFactory(GsonConverterFactory.create) //使用Gson

.addCallAdapterFactory(RxJava2CallAdapterFactory.create) //使用RxJava

.build;

finalWeatherService weatherService = retrofit.create(WeatherService.class);

//第一次网络请求,获取天气数据

weatherService.getCall(cityId)

.subscribeOn(Schedulers.io)

.flatMap((Function>) weatherBean -> {

...

for(WeatherBean.DataBean.ForecastBean forecastBean : weatherBean.getData.getForecast) {

...

//循环取出每一天的天气类型,添加url

String url = "https://cdn.heweather.com/cond_icon/"+ preferences.getString(forecastBean.getType, "未知"

//使用Glide通过天气类型进行第二次网络请求,获取类型对应天气图标bitmap

FutureTarget target = Glide.with(getApplicationContext)

.asBitmap

.load(url)

.submit;

finalBitmap bitmap = target.get;

dataArrayList.add( newMyCurveView.WeatherData(low, high, date, type, bitmap));

}

...

//将请求返回的天气数据继续传至主线程的Observer观察者

returnObservable.fromArray(weatherBean);

.observeOn(AndroidSchedulers.mainThread) //切换至主线程

.subscribe( newObserver {

@Override

publicvoidonSubscribe(Disposable d){

}

@SuppressLint( "SetTextI18n")

@Override

publicvoidonNext(WeatherBean weatherBean){

//在主线程中处理得到的数据

}

@Override

publicvoid(Throwable e){

}

@Override

publicvoidonComplete{

}

});

}

自定义View

布局中间展示未来15天天气,数据有日期、最高温度、最低温度、类型、类型图标,其中温度连成两条曲线,整体支持滑动。

我是这样设计的,温度曲线初始为两条直线,为这15天的平均值,然后开始变化,变到对应的值,从而形成曲线效果。

新建MyCurveView.java,继承自View。添加WeatherData内部类,添加对应的属性及get、set方法。

staticclassWeatherData{

privatefloatlowTemp;

privatefloathighTemp;

privateintdate;

privateString type;

privateBitmap typeBitmap;

WeatherData( floatlowTemp, floathighTemp, intdate, String type, Bitmap typeBitmap) {

this.lowTemp = lowTemp;

this.highTemp = highTemp;

this.date = date;

this.type = type;

this.typeBitmap = typeBitmap;

}

...

}

添加setProgress方法,在网络请求完毕后,调用该方法更新数据和UI。首先调用arrayList保存网络数据,然后在动画中不断更新视图。

publicvoidsetProgress(intaverageHigh, intaverageLow, finalintlow, inttop, ArrayList innerData){

arrayList(innerData, top, low, averageHigh, averageLow);

ValueAnimator animatorHigh = ValueAnimator.ofInt( 0, top);

animatorHigh.setDuration( 1000);

animatorHigh.setInterpolator( newAccelerateInterpolator);

animatorHigh.addUpdateListener(valueAnimator -> {

mHighPercent = ( int)valueAnimator.getAnimatedValue;

invalidate;

});

ValueAnimator animatorLow = ValueAnimator.ofInt( 0, low);

animatorLow.setDuration( 1000);

animatorLow.setInterpolator( newAccelerateInterpolator);

animatorLow.addUpdateListener(valueAnimator -> {

mLowPercent = ( int)valueAnimator.getAnimatedValue;

});

AnimatorSet set = newAnimatorSet;

//两个动画同时进行

set.playTogether(animatorHigh, animatorLow);

//监听动画

set.addListener( newAnimator.AnimatorListener {

@Override

publicvoidonAnimationStart(Animator animation){

//在做动画的时间内,通过该标志位禁止触摸动作

isAnimation = true;

}

@Override

publicvoidonAnimationEnd(Animator animation){

isAnimation = false;

}

@Override

publicvoidonAnimationCancel(Animator animation){

}

@Override

publicvoidonAnimationRepeat(Animator animation){

}

});

set.start;

}

arrayList方法,除了保存数据外,将温度做个转换,因为初始是从平均值开始变的,mHighPercent在1s的时间内从0变为15日最高温度值,mHighPercent * (innerData.get(i).getHighTemp - averageHigh) / (max - 0)可以做到在1s的时间内,将当日最高温度从平均值变为实际值,当日最低温度同理。

@SuppressWarnings( "PointlessArithmeticExpression")

privatevoidarrayList(ArrayList innerData, intmax, intmin, intaverageHigh, intaverageLow){

high = averageHigh;

low = averageLow;

dataArray.clear;

//保存网络数据

dataArray.addAll(innerData);

for( inti = 0; i < innerData.size; i++) {

//在1s的变化时间内,将值从平均值变为实际值

dataArray. get(i).setHighTemp((innerData. get(i).getHighTemp - averageHigh) / (max - 0));

//在1s的变化时间内,将值从平均值变为实际值

dataArray. get(i).setLowTemp((averageLow - innerData. get(i).getLowTemp) / (min - 0));

}

}

重写onMeasure。

@Override

protectedvoidonMeasure(intwidthMeasureSpec, intheightMeasureSpec){

intwidthSize = MeasureSpec.getSize(widthMeasureSpec);

intheightSize = MeasureSpec.getSize(heightMeasureSpec);

measureWidth = widthSize;

measureHeight = heightSize;

//一个页面展示6天的温度信息,每一天为宽度为mTempWidth

mTempWidth = measureWidth / 6;

setMeasuredDimension(widthSize, heightSize);

}

重写onDraw。

@SuppressLint( "DrawAllocation")

@ Override

protectedvoidonDraw(Canvas canvas) {

super.onDraw(canvas);

//为请求到网络数据时,页面显示文字

if(dataArray.size <= 0

drawNoDataText(canvas);

} else{

floatstartX = mStartX + (mTempWidth / 2);

for( inti = 0; i < dataArray.size; i++) {

//绘制天气图标

RectF rectF = newRectF(startX - 30, 300, startX + 30, 360);

canvas.drawBitmap(dataArray. get(i).getTypeBitmap, null, rectF, mCurvePaint);

//绘制最高温度

floathighTextWidth = mTempTextPaint.measureText(( int)(high + mHighPercent * dataArray. get(i).getHighTemp) + "");

floathighTextStartX = startX - highTextWidth / 2;

drawTempText(canvas, ( int)(high + getHighTempByPercent(i)) + "", highTextStartX, ( 140- curve_ratio * getHighTempByPercent(i)));

//curve_ratio为可变值,用于调整显示效果。该值越大温度差效果越明显。

canvas.drawCircle(startX, ( 160- curve_ratio * getHighTempByPercent(i)), 5, circlePaint);

//第一段曲线

if(i == 0) {

highPath.moveTo(startX, ( 160- curve_ratio * getHighTempByPercent(i)));

highControlPt1X = startX + mTempWidth / 4;

highControlPt1Y = 160- curve_ratio * getHighTempByPercent(i);

highControlPt2X = startX + (mTempWidth / 4) * 3;

highControlPt2Y = (( 160- curve_ratio * getHighTempByPercent(i + 1))) - ((( 160- curve_ratio * getHighTempByPercent(i + 2))) - (( 160- curve_ratio * getHighTempByPercent(i)))) / 4;

//3阶贝塞尔曲线

highPath.cubicTo(

highControlPt1X, highControlPt1Y,

highControlPt2X, highControlPt2Y,

startX + mTempWidth, ( 160- curve_ratio * getHighTempByPercent(i + 1)));

canvas.drawPath(highPath, mCurvePaint);

//每次绘制后将画笔恢复

highPath.reset;

}

//中间曲线

if(i != 0&& i < dataArray.size - 2) {

highPath.moveTo(startX, ( 160- curve_ratio * getHighTempByPercent(i)));

highControlPt1X = startX + mTempWidth / 4;

highControlPt1Y = (( 160- curve_ratio * getHighTempByPercent(i))) + ((( 160- curve_ratio * getHighTempByPercent(i + 1))) - (( 160- curve_ratio * getHighTempByPercent(i - 1)))) / 4;

highControlPt2X = startX + (mTempWidth / 4) * 3;

highControlPt2Y = (( 160- curve_ratio * getHighTempByPercent(i + 1))) - ((( 160- curve_ratio * getHighTempByPercent(i + 2))) - (( 160- curve_ratio * getHighTempByPercent(i)))) / 4;

highPath.cubicTo(

highControlPt1X, highControlPt1Y,

highControlPt2X, highControlPt2Y,

startX + mTempWidth, ( 160- curve_ratio * getHighTempByPercent(i + 1)));

canvas.drawPath(highPath, mCurvePaint);

highPath.reset;

}

//最后一段曲线

if(i == dataArray.size - 2) {

highPath.moveTo(startX, ( 160- curve_ratio * getHighTempByPercent(i)));

highControlPt1X = startX + mTempWidth / 4;

highControlPt1Y = (( 160- curve_ratio * getHighTempByPercent(i))) + ((( 160- curve_ratio * getHighTempByPercent(i + 1))) - (( 160- curve_ratio * getHighTempByPercent(i - 1)))) / 4;

highControlPt2X = startX + (mTempWidth / 4) * 3;

highControlPt2Y = 160- curve_ratio * getHighTempByPercent(i + 1);

highPath.cubicTo(

highControlPt1X, highControlPt1Y,

highControlPt2X, highControlPt2Y,

startX + mTempWidth, ( 160- curve_ratio * getHighTempByPercent(i + 1)));

canvas.drawPath(highPath, mCurvePaint);

highPath.reset;

}

//绘制最低温度,与绘制最高温度类似

...

//绘制日期

floatdayTextWidth = mTextPaint.measureText(dataArray. get(i).getDate + "日");

floatdayStartX = startX - dayTextWidth / 2;

floatdayTextStartY = 40+ getFontAscentHeight(mTextPaint);

drawDayText(canvas, dataArray. get(i).getDate + "日", dayStartX, dayTextStartY);

//绘制天气

floattypeTextWidth = mTextPaint.measureText(dataArray. get(i).getType);

floattypeTextStartX = startX - typeTextWidth / 2;

floattypeTextStartY = measureHeight - 40- getFontDescentHeight(mTextPaint);

canvas.drawText(dataArray. get(i).getType, typeTextStartX, typeTextStartY, mTextPaint);

//每绘制完一天,往后移动mTempWidth距离,绘制下一天

startX = startX + mTempWidth;

}

}

}

其中的一些参数是可以根据需要更改的。

重写dispatchTouchEvent。当滑动到最左边也就是第一天的时候,应该禁止继续向右继续滑动。滑动到最右边同理。

@Override

publicbooleandispatchTouchEvent(MotionEvent ev){

intdispatchCurrX = ( int) ev.getX;

intdispatchCurrY = ( int) ev.getY;

switch(ev.getAction) {

...

caseMotionEvent.ACTION_MOVE:

floatdeltaX = dispatchCurrX - dispatchTouchX;

floatdeltaY = dispatchCurrY - dispatchTouchY;

//竖直滑动的父容器拦截事件

if(Math.abs(deltaY) - Math.abs(deltaX) > 0) {

getParent.requestDisallowInterceptTouchEvent( false);

}

//向右滑动,滑动到左边边界,父容器进行拦截

if((dispatchCurrX - dispatchTouchX) > 0&& mStartX == 0) {

getParent.requestDisallowInterceptTouchEvent( false);

} elseif((dispatchCurrX - dispatchTouchX) < 0&& mStartX == -getMoveLength) {

//向左滑动,滑动到右边边界,父容器进行拦截

getParent.requestDisallowInterceptTouchEvent( false);

}

break;

...

}

dispatchTouchX = dispatchCurrX;

dispatchTouchY = dispatchCurrY;

returnsuper.dispatchTouchEvent(ev);

}

重写onTouchEvent。

@SuppressLint( "ClickableViewAccessibility")

@Override

publicbooleanonTouchEvent(MotionEvent event){

//如果正在执行动画,直接返回

if(isAnimation) {

returntrue;

}

if(mVelocityTracker == null) {

mVelocityTracker = VelocityTracker.obtain;

}

mVelocityTracker.addMovement(event);

switch(event.getAction) {

caseMotionEvent.ACTION_DOWN:

lastX = event.getX;

//当点击的时候,判断如果是在fling的效果的时候,就停止快速滑动

if(isFling) {

removeCallbacks(mScrollRunnable);

isFling = false;

}

break;

caseMotionEvent.ACTION_MOVE:

floatcurrX = event.getX;

mStartX += currX - lastX;

//计算每次滑动后的mStartX

//向右滑动

if((currX - lastX) > 0) {

if(mStartX > 0) {

mStartX = 0;

}

} else{ //向左滑动

if(-mStartX > getMoveLength) {

mStartX = -getMoveLength;

}

}

lastX = currX;

break;

caseMotionEvent.ACTION_UP:

//1s内的滑动速度

mVelocityTracker.computeCurrentVelocity( 1000);

//计算快速滑动的速度,如果是大于某个值,并且数据的长度大于整个屏幕的长度,那么就允许有fling后逐渐停止的效果

if(Math.abs(mVelocityTracker.getXVelocity) > 100

&& !isFling && measureWidth < dataArray.size * mTempWidth) {

mScrollRunnable = newScrollRunnable(mVelocityTracker.getXVelocity / 5);

this.post(mScrollRunnable);

}

break;

caseMotionEvent.ACTION_CANCEL:

break;

}

returntrue;

}

以上为部分主要代码,自定义View就算是完成了。

城市搜索

使用单独一个Activity,使用了DataBinding来做搜索编辑框的绑定,RecyclerView用来展示返回的城市列表,选择其中的某一城市后,通过EventBus将城市信息通知MainActivity。新建CityActivity,添加CityViewHolder类,并在其中添加afterTextChanged(Editable s)方法,在onCreate中完成代码和视图的绑定。

publicclassCityActivityextendsAppCompatActivity{

...

@Override

protectedvoidonCreate(@Nullable Bundle savedInstanceState){

super.onCreate(savedInstanceState);

//绑定代码和视图

ActivityCityBinding activityCityBinding = DataBindingUtil.setContentView( this, R.layout.activity_city);

activityCityBinding.setCityViewHolder( newCityViewHolder);

...

}

...

publicclassCityViewHolder{

publicvoidafterTextChanged(Editable s){

}

}

}

修改对应的activity_city.xml

name="cityPresenter"

type="com.sk.simpleweather.CityActivity.CityViewHolder"/>

android:layout_width="match_parent"

android:layout_height="match_parent"

android:layout_marginTop="20dp"

android:layout_marginStart="30dp"

android:layout_marginEnd="30dp"

android:orientation="vertical">

android:id="@+id/search_edittext"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:padding="5dp"

android:background="@drawable/et_bgd"

android:focusable="true"

android:textSize="16sp"

android:maxLines="1"

android:singleLine="true"

android:hint="输入城市"

android:textColorHint="#40000000"

android:afterTextChanged="@{cityPresenter.afterTextChanged}"/>

...

这样,每次修改EditText都会走afterTextChanged。

接下完成搜索城市的请求。新建CityBean.java,和上面一样,通过GsonFormat自动生成代码,Json数据可以在和风天气的接口文档的数据返回示例中看到。接下来新建CityService.java,添加getCall方法。查看和风天气的接口文档,location和key是必选的,通过@Query注解添加请求URL中的这两个参数。返回类型中的泛型为刚刚完成的CityBean类。

publicinterfaceCityService{

@GET("find")

Observable getCall( @Query("location")String location,

@Query("key")String key);

}

实现afterTextChanged,和上面天气数据请求基本一致。

publicvoidafterTextChanged(Editable s){

...

//如果输入为空,直接返回

if(s.toString.equals( "")) {

return;

}

Retrofit retrofit = newRetrofit.Builder

.baseUrl( "https://search.heweather.net/")

.addConverterFactory(GsonConverterFactory.create) //使用Gson

.addCallAdapterFactory(RxJava2CallAdapterFactory.create) //使用RxJava

.build;

CityService cityService = retrofit.create(CityService.class);

cityService.getCall(s.toString, KEY)

.subscribeOn(Schedulers.io)

.observeOn(AndroidSchedulers.mainThread)

.subscribe( newObserver {

@Override

publicvoidonSubscribe(Disposable d){

}

@Override

publicvoidonNext(CityBean cityBean){

//对获取到的数据进行处理

}

@Override

publicvoid(Throwable e){

}

@Override

publicvoidonComplete{

...

}

});

}

新建CitysAdapter.java,继承自RecyclerView.Adapter,作为RecyclerView的Adapter。

publicclassCitysAdapterextendsRecyclerView.Adapter{

privateArrayList citys;

privateContext mContext;

privateOnItemClick mOnItemClick;

CitysAdapter(ArrayList citys, Context parentContext) {

this.citys = citys;

mContext = parentContext;

}

voidsetOnItemClick(OnItemClick onItemClick){

mOnItemClick = onItemClick;

}

interfaceOnItemClick{

//回调函数,在CityActivity中实现该方法。

voidonClick(String city, intposition);

}

staticclassViewHolderextendsRecyclerView.ViewHolder{

privateLinearLayout layout;

privateTextView textView;

ViewHolder( @NonNullView itemView) {

super(itemView);

layout = itemView.findViewById(R.id.city_layout);

textView = itemView.findViewById(R.id.city);

}

}

@NonNull

@Override

publicViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, inti){

if(mContext == null) {

mContext = viewGroup.getContext;

}

View view = LayoutInflater.from(mContext).inflate(R.layout.city_item, viewGroup, false);

returnnewViewHolder(view);

}

@Override

publicvoidonBindViewHolder(@NonNull ViewHolder viewHolder, intposition){

viewHolder.textView.setText(citys.get(position));

viewHolder.layout.setOnClickListener(v -> mOnItemClick.onClick(citys.get(position), position));

}

@Override

publicintgetItemCount{

returncitys.size;

}

}

添加CityMessageEvent.java,供EventBus使用。

publicclassCityMessageEvent{

privateString name;

privateString cityId;

//name和cityId的set、get方法

...

}

在CityActivity中实现上面的onClick方法。

privateCityMessageEvent messageEvent;

@ Override

publicvoidonClick(String city, intposition) {

if(mCityBean != null) {

messageEvent.setName(city);

messageEvent.setCityId(mCityBean.getHeWeather6. get( 0).getBasic. get(position).getCid.replace( "CN", ""));

//通过EventBus发射event

EventBus.getDefault.postSticky(messageEvent);

}

finish;

}

修改MainActivity,添加onMessageEvent方法,用来接收EventBus发出的event。接收到数据后,再次请求网络数据。

@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)

publicvoidonMessageEvent(CityMessageEvent messageEvent){

requestWeather(messageEvent.getCityId);

}

最后附上源码地址,有什么疑问及意见欢迎大家指出,后续有时间会对代码进行改进。要运行的话,麻烦在和风天气官网注册一下,自己新建一个工程,使用自己的Key,谢谢~~

项目地址:GitHub

版权声明:本文为CSDN博主「Kai_0825」的原创文章。

原文链接:https://blog.csdn.net/songkai0825/article/details/95257837 返回搜狐,查看更多

责任编辑:

android天气搜索框,Android简易天气App相关推荐

  1. Android 系统搜索框(有浏览记录)

    实现Android 系统搜索框(有浏览记录),先看下效果: 一.配置搜索描述文件 要在res中的xml文件加创建sreachable.xml,内容如下: <?xml version=" ...

  2. android 仿搜索动画,Android仿京东顶部搜索框滑动伸缩动画效果

    最近使用京东发现,京东顶部的搜索框有一个新的伸缩效果,根据用户的手势滑动,伸缩搜索框.觉得效果还不错,就看了下其他的应用有没有这种伸缩的效果,发现安居客也使用了类似的一种效果,然后就想着实现这样的一种 ...

  3. Android SearchView 搜索框

    如果对这个效果感觉不错, 请往下看. 背景: 天气预报app, 本地数据库存储70个大中城市的基本信息, 根据用户输入的或通过搜索框选取的城市, 点击查询按钮后, 异步请求国家气象局数据, 得到返回的 ...

  4. android 系统搜索框(有浏览记录),Android 系统有浏览记录搜索框

    一.配置搜索描述文件 要在res中的xml文件加创建sreachable.xml,内容如下: xmlns:android="http://schemas.android.com/apk/re ...

  5. android动态文本框,Android文本框实现搜索和清空效果

    本文实现的效果: 文本框输入为空时显示输入的图标:不为空时显示清空的图标,此时点击清空图标能清空文本框内输入文字. 实现效果: 核心代码: package com.example.test; impo ...

  6. 简简单单搞一个实用的Android端搜索框

    Hello啊老铁们,今天带来一个非常实用的自定义搜索框,包含了搜索框.热门搜索列表.最近搜索列表等常见的功能,有类似的,大家可以直接复用,将会大大节约您的开发时间,有一点,很负责任的告诉大家,实现这个 ...

  7. android 自定义搜索框edittext,Android编程自定义搜索框实现方法【附demo源码下载】...

    本文实例讲述了Android编程自定义搜索框实现方法.分享给大家供大家参考,具体如下: 先来看效果图吧~ 分析:这只是模拟了一个静态数据的删除与显示 用EditText+PopupWindow+lis ...

  8. Android 自定义搜索框

    1. 准备一个边框xml 新建xml, 命名 layout_border.xml, 内容如下: <?xml version="1.0" encoding="utf- ...

  9. android bilibili搜索框,仿bilibili搜索框效果(三句代码实现)

    SearchDialog 仿bilibili搜索框效果(只需要三句话即可实现) 先看预览图(转换后有一点点失真): 前言 1,支持搜索历史(已经做了数据库存储了) 2,基本与bilibili的搜索效果 ...

最新文章

  1. 信息树和XML文件的遍历及XML文件的应用
  2. 中国非动物胶市场来产销需求及发展潜力研究报告2022版
  3. 我创意很大,玩转《猫和老鼠》手游,瓜分40万大奖
  4. java 基本数据类型 容器_Java 基本数据类型
  5. mysql unauthenticated user原因分析以及解决方法
  6. gradle学习(19)-log系统
  7. select在各个浏览器中的兼容性问题
  8. 2018华为数通技术大赛复赛拓扑具体配置
  9. 自媒体图文、视频素材网站哪里找?这些非常好用
  10. Excel如何删除表格中的空白列
  11. Oracle客户端安装教程(图文)
  12. iOS:iPhone XR、iPhoneXS、iPhone XS Max屏幕适配
  13. 字节跳动-后端开发岗最新春招面经分享,四面拿下,有惊无险
  14. python三级联动菜单_Excel–这才是三级联动下拉菜单的正确做法
  15. 1.3读论文笔记:M. Raissi a等人的Physics-informed neural networks:A deep learning framework for solving forw..
  16. linux awk统计文本单词,shell统计文本中单词的出现次数
  17. CSP-J复赛复习题目(NOIP普及组2000-2011)
  18. NTT(快速数论变换)模板
  19. 浅议信息系统控制在企业中的应用(lunwen+开题报告)
  20. 那些年啊,那些事——一个程序员的奋斗史 ——26

热门文章

  1. 调用接口登录禅道_调用禅道api创建新用户
  2. 平衡小车制作系列之八——总结
  3. I2C接口配置ES7243录音芯片,MCU(STM32)收不到I2C ACK的问题
  4. 酷!使用 jQuery Canvas 制作相机快门效果
  5. lightGBM+158个技术因子实证A股十年数据:年化24%,回撤10%
  6. 开发qt程序所需要注意的
  7. 游戏开发周志(1.2-1.8)
  8. Arthas 命令解析(watch/tt/sc)
  9. 全面解析Notification
  10. 重点国有林区林业局87个林业局名录