2015-8-6 15:07:54
找不到词库,暂时找到一个带音标和释义的考研单词excel(估计是好几年前的大纲词汇),就先用这个吧。 excel不能显示音标的话,还得下载字体TOPhonetic.ttf。
excel导入SQLite 试了几个可视化工具,就SQLiyeStudio比较满意,也没有乱码。 开始时把excel另存为.csv文件,系统的分隔符是逗号,但是去控制面板改了,excel导出时还是逗号,不知道为什么,可能需要重启电脑吧,懒得重启。竟然忘记replace了!所以excel → 制表符分隔的txt → replace分隔符,就ok了^_^
2015-8-10
关于引入V7包:http://wp.aesean.com/?p=185
2015-8-11
继承RecyclerView.Adapter出现The hierarchy of the type ViewHolder is inconsistent,因为菜单用的SlidingMenu,support-v4包可能不是最新的,将SlidingMenu的libs下的v4包替换成最新的就可以了。Call requires API level 21 (current min is 17): android.content.Context#getDrawable 解决
ContextCompat.getDrawable(this, R.drawable.your_drawable)
2015-8-12
1.actionbar.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_vertical" android:orientation="horizontal" > <TextView android:id="@+id/action_bar_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="下拉列表" /> <!-- 下拉列表 --> <Spinner android:id="@+id/action_bar_spinner" android:layout_width="wrap_content" android:layout_height="wrap_content" > </Spinner> </LinearLayout>2.在activity的onCreate()或fragment的onCreateView()中添加代码,初始化下拉列表数据
//使自定义的普通View能在title栏显示,actionBar.setCustomView能起作用 getActivity().getActionBar().setDisplayShowCustomEnabled(true); //初始化下拉列表 View actionbarLayout = view.inflate(activity,R.layout.actionbar, null); mActionbarSpinner = (Spinner) actionbarLayout.findViewById(R.id.actionbar_spinner); //初始化下拉列表数据 //方法一 initSpinnerMethod1(); //方法二 //initSpinnerMethod2(); //事件监听 mActionbarSpinner.setOnItemSelectedListener(new SpinnerItemSelectedListener()); //在Bar上显示定制view actionBar.setCustomView(actionbarLayout);初始化下拉列表数据: (1) 在strings.xml添加数组
<string-array name="spinner_list" > <item>item1</item> <item>item2</item> <item>item3</item> </string-array>代码:
private void initSpinnerMethod1() { String[] mItems = getResources().getStringArray(R.array.spinner_list); ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<String>(activity,android.R.layout.simple_spinner_item, mItems); spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mActionbarSpinner.setAdapter(spinnerAdapter); }(2)在代码中添加数组
private List<String> getData(){ List<String> data = new ArrayList<String>(); data.add("item1"); data.add("item2"); data.add("item3"); return data; } private void initSpinnerMethod2() { mActionbarSpinner.setAdapter( new ArrayAdapter<String>(activity, android.R.layout.simple_expandable_list_item_1,getData())); }3.监听action_bar的spinner的item选择事件
private class SpinnerItemSelectedListener implements OnItemSelectedListener { @Override public void onItemSelected(AdapterView<?> arg0, View view, int position,long arg3) { String str= arg0.getItemAtPosition(position).toString(); Toast.makeText(MainActivity.this, "你选择的是:"+str, 2000).show(); } @Override public void onNothingSelected(AdapterView<?> arg0) {} }在单词本界面的actionbar想显示一个spinner下拉选择单词本(暂时未加入切换单词本的功能),在WordBookFragment的onCreateView()里设置自定义actionbar
getActivity().getActionBar().setDisplayShowCustomEnabled(true);别的fragment里设置
getActivity().getActionBar().setDisplayShowCustomEnabled(false);或者加载另外的view。
分别在onCreateView()里设置
getActivity().setTitle("标题");若写成
getActivity().getActionBar().setTitle("标题");会将所有fragment设置成同一标题
cannot convert from android.support.v7.app.ActionBar to android.app.ActionBar
ActionBar actionBar = getActivity.getActionBar();使用quick fix:
android.app.ActionBar actionBar;2015-8-18
使用RelativeLayout
android:layout_alignParentRight="true"2015-8-19
使用SharedPreferences,在onCreate()里:
SharedPreferences preferences; Editor editor; if (preferences.getBoolean("firststart", true)) { //获取boolean值,可为缺省值,若缺省,则返回参数二的值 editor = preferences.edit(); editor.putBoolean("firststart", false); //若是首次,则改为false editor.commit(); //提交 }2015-8-20
低于API 11
notification.flags |= Notification.FLAG_NO_CLEAR;高于API 11 或者 使用Android Support Library
//获取状态通知栏管理 NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); //实例化通知栏构造器 NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this); mBuilder.setSmallIcon(R.drawable.ic_launcher);//设置通知小图标 .setContentTitle("标题") //设置通知栏标题 .setContentText("内容") //设置通知栏显示内容 .setContentIntent(getDefalutIntent(Notification.FLAG_AUTO_CANCEL)) //设置通知栏点击意图 .setNumber(number) //设置通知集合的数量 .setTicker("通知来啦") //通知首次出现在通知栏,带上升动画效果的 .setWhen(System.currentTimeMillis())//通知产生的时间,会在通知信息里显示,一般是系统获取到的时间 .setPriority(Notification.PRIORITY_DEFAULT) //设置该通知优先级 .setAutoCancel(true)//设置这个标志当用户单击面板就可以让通知将自动取消 .setOngoing(false)//ture,设置他为一个正在进行的通知。他们通常是用来表示一个后台任务,用户积极参与(如播放音乐)或以某种方式正在等待,因此占用设备(如一个文件下载,同步操作,主动网络连接) .setDefaults(Notification.DEFAULT_VIBRATE)//向通知添加声音、闪灯和振动效果的最简单、最一致的方式是使用当前的用户默认设置,使用defaults属性,可以组合 //Notification.DEFAULT_ALL Notification.DEFAULT_SOUND 添加声音 // requires VIBRATE permission在service里
startForeground(mID, notification); //设置前台服务必须在AndroidManifest.xml中注册
<service android:name="完整包名.ServiceNotification" />在onItemSelected()中
int userChoice = spinner.getSelectedItemPosition(); SharedPreferences sharedPref = getSharedPreferences("FileName",0); SharedPreferences.Editor prefEditor = sharedPref.edit(); prefEditor.putInt("userChoiceSpinner",usersChoice); prefEditor.commit();当从Paused状态恢复activity时,系统会调用onResume()方法。 系统每次调用onResume()时,activity都处于前台,包括第一次创建的时候。所以,应该实现onResume()来初始化那些在onPause方法里面释放掉的组件,并执行那些activity每次进入Resumed state都需要的初始化动作 (例如开始动画与初始化那些只有在获取用户焦点时才需要的组件)
大多数app并不需要实现这个方法,因为局部类的references会随着activity的销毁而销毁,并且我们的activity应该在onPause()与onStop()中执行清除activity资源的操作。然而,如果activity含有在onCreate调用时创建的后台线程,或者是其他有可能导致内存泄漏的资源,则应该在OnDestroy()时进行资源清理,杀死后台线程。
除非程序在onCreate()方法里面就调用了finish()方法,系统通常是在执行了onPause()与onStop()之后再调用onDestroy()。在某些情况下,例如我们的activity只是做了一个临时的逻辑跳转的功能,它只是用来决定跳转到哪一个activity,这样的话,需要在onCreate里面调用finish方法,这样系统会直接调用onDestory,跳过生命周期中的其他方法。
参考:android 使用AlarmManager定时启动service
通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,但此时并没有运行,它需要CPU时间片。一旦得到CPU时间片,就会执行run()方法。run()的方法体称为线程体,它包含了要执行的这个线程的内容,run()方法运行结束,此线程也随即终止。
Android 平台上常用的定时器主要有两个:
Java的TimerAndroid的AlarmManagerJava的Timer类可以用来计划需要循环执行的任务。
简单的说,一个Timer内部封装装了“一个Thread”和“一个TimerTask队列”,这个队列按照一定的方式将任务排队处理。封装的Thread在Timer的构造方法调用时被启动,这个Thread的run方法按照条件去循环这个TimerTask队列,然后调用TimerTask的run方法。
但是,如果CPU进入了休眠状态,那么这个thread将会因为失去CPU时间片而阻塞,从而造成我们需要的定时任务失效。上述定时任务失效的场景分析:假设定时任务的条件是到了时间xx:yy才能执行,但由于cpu休眠造成线程阻塞的关系,当前系统时间超过了这个时间,即便CPU从终端中恢复了,那么由于条件不满足,定时任务在这一次自然就失效了。
解决方案:它需要用WakeLock让CPU保持唤醒状态。但这样会大量消耗手机电量,大大减短手机待机时间。这种方式不能满足我们的需求。
注:TimerTask实现Runnable接口,但它的run方法只是简单的在Timer中封装的Thread中被调用,并未将其放在其它线程中执行。也就是说timer是单线程执行的,那么问题来了,为何要这么费劲的封装一个Runnable接口又不进行多线程调用?
AlarmManager是Android 系统封装的用于管理RTC的模块,RTC(Real Time Clock)是一个独立的硬件时钟,可以在 CPU休眠时正常运行,在预设的时间到达时,通过中断唤醒CPU。这意味着,如果我们用 AlarmManager来定时执行任务,CPU 可以正常的休眠,只有在需要运行任务时醒来一段很短的时间。
参考:Timer与AlarmManager的区别
2015-8-21
在Service的onStartCommand()里更新每次Notification需要更新的内容,如单词信息。无需改动的信息在onCreate()里初始化。
原代码:
@Override public int onStartCommand(Intent intent, int flags, int startId) { mBuilder .setContentText(WordsDB.wordClass.toString()) //测试用的单词信息 .setWhen(System.currentTimeMillis()) //更新的时间 .setTicker(WordsDB.wordClass.toString()); //在通知栏动画向上弹出 startForeground(notifyID, mBuilder.build()); //display in "ongoing" Log.d("通知栏单词", WordsDB.wordClass.toString()); return super.onStartCommand(intent, flags, startId); }按钮点击时会更新notification,也会在通知栏弹出提示。 但使用AlarmManager定时启动该service时,会更新内容,但不会在通知栏弹出提示,只能自己打开通知栏才能查看到更新。
改动:使用NotificationManager.notify()
@Override public int onStartCommand(Intent intent, int flags, int startId) { mBuilder .setContentText(WordsDB.wordClass.toString()) .setWhen(System.currentTimeMillis()) .setTicker(WordsDB.wordClass.toString()); //popup in Status Bar mNotificationManager.notify(notifyID, notification); //update data startForeground(notifyID, mBuilder.build()); //display in "ongoing" Log.d("通知栏单词", WordsDB.wordClass.toString()); return super.onStartCommand(intent, flags, startId); }notify必须在startForeground前面,先更新通知的内容,再显示在前台。 notifyID需一致。
参考:How do I update the notification text for a foreground service in Android?
2015-8-22
用了3个EditText,分别输入小时、分钟、秒,默认30秒更新一次,即00:00:30 获取焦点时自动清空EditText,失去焦点时若未输入任何内容,则回到默认值
在其parent view设置xml属性:
android:clickable="true" android:focusable="true" android:focusableInTouchMode="true"1.在parent view设置
android:clickable="true" android:focusableInTouchMode="true"2.hideKeyboard() method
public void hideKeyboard(View view) { InputMethodManager inputMethodManager =(InputMethodManager)getSystemService(Activity.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); }参考:how to hide soft keyboard on android after clicking outside EditText?
2015-8-28
1.动态注册的广播永远要快于静态注册的广播,不管静态注册的优先级设置的多高,不管动态注册的优先级有多低
2.动态注册广播不是常驻型广播,也就是说广播跟随activity的生命周期。 注意: 在activity结束前,移除广播接收器。
静态注册是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。
3.在同一个优先级下,谁先启动的快,谁将先接收到广播。
动态注册代码:
MyBroadcastReceiver broadcast= new MyBroadcastReceiver(); IntentFilter filter = new IntentFilter("action_name"); registerReceiver(broadcast, filter);静态注册代码(在Manifest.xml中添加):
<receiver android:name="com.example.MyReceiver" > <intent-filter> <action android:name="action_name" /> //可自定义action_name </intent-filter> </receiver>2015-8-29
在设置如下时
sm.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN); sm.setTouchModeBehind(SlidingMenu.TOUCHMODE_FULLSCREEN);1.菜单里的listview无法点击,解决: 在Slidingmenu_lib里,
修改CustomViewAbove.java,将onInterceptTouchEvent()和onTouchEvent()的case MotionEvent.ACTION_DOWN:的break;改为return mQuickReturn; 去掉initCustomViewAbove()里的setInternalPageChangeListener()修改CustomViewBehind.java,将onInterceptTouchEvent()和onTouchEvent()的return分别改为return mViewAbove.onInterceptTouchEvent(e);和return mViewAbove.onTouchEvent(e);2.上述步骤后,若aboveview无法滑动,则在aboveview的root layout里添加android:clickable="true"
参考:All touch events are consumed by CustomViewAbove #446
2015-8-31
参考教程:Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果
1.在Manifest.xml声明权限
<!-- 显示顶层浮窗 --> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />注意:在MIUI上需要在设置中打开本应用的“显示悬浮窗”开关,并且重启应用,否则悬浮窗只能显示在本应用界面内,不能显示在手机桌面上。 2.获取WindowManager
// 获取应用的Context Context mContext = context.getApplicationContext(); // 获取WindowManager WindowManager mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);3.获取layout
LayoutInflater.from(mContext).inflate(R.layout.float_window, this); View view = findViewById(R.id.window_layout);4.参数设置
smallWindowParams = new LayoutParams(); //LayoutParams.TYPE_PHONE: 全屏区域显示(不含状态栏) //LayoutParams.TYPE_SYSTEM_ALERT: 全屏区域显示(包含状态栏区域,被状态栏覆盖) //LayoutParams.TYPE_SYSTEM_ERROR: 全屏区域显示(包含状态栏区域,覆盖在状态栏上) smallWindowParams.type = LayoutParams.TYPE_SYSTEM_ERROR; //设置图片格式:背景透明 smallWindowParams.format = PixelFormat.RGBA_8888; //FLAG_LAYOUT_IN_SCREEN:可在状态栏上显示 smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_LAYOUT_IN_SCREEN; smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP; smallWindowParams.width = ViewSmallFloatWindow.viewWidth; smallWindowParams.height = ViewSmallFloatWindow.viewHeight; smallWindowParams.x = 0; smallWindowParams.y = Util.getStatusBarHeight(context);注:
这样设置下拉通知栏也会显示在悬浮窗下层。FLAG_NOT_TOUCH_MODAL和FLAG_NOT_FOCUSABLE任选其一都可以,若只设置FLAG_LAYOUT_IN_SCREEN会导致悬浮窗的焦点变成全屏(悬浮窗外无法操作)。smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_LAYOUT_IN_SCREEN | LayoutParams.FLAG_LAYOUT_INSET_DECOR | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;也可以。WindowManager.LayoutParams这个类用于提供悬浮窗所需的参数,其中有几个经常会用到的变量:
type:确定悬浮窗的类型,一般设为2002,表示在所有应用程序之上,但在状态栏之下。
flags:确定悬浮窗的行为,比如说不可聚焦,非模态对话框等等,属性非常多,大家可以查看文档。
gravity:确定悬浮窗的对齐方式,一般设为左上角对齐,这样当拖动悬浮窗的时候方便计算坐标。
x:确定悬浮窗的位置,如果要横向移动悬浮窗,就需要改变这个值。
y:确定悬浮窗的位置,如果要纵向移动悬浮窗,就需要改变这个值。
width:指定悬浮窗的宽度。
height:指定悬浮窗的高度。
Display中getHeight()和getWidth()被废弃
Display dp=getWindowManager().getDefaultDisplay(); int Height=dp.getHeight(); ---->The method getHeight() from the type Display is deprecated int Width=dp.getWidth(); ---->The method getWidth() from the type Display is deprecated替代的方法:
//WindowManager wm = this.getWindowManager(); WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); //DisplayMetrics dm = new DisplayMetrics(); //wm.getDefaultDisplay().getMetrics(dm); DisplayMetrics dm = Resources.getSystem().getDisplayMetrics(); SCREEN_WIDTH = dm.widthPixels; SCREEN_HEIGHT = dm.heightPixels;解析heightPixels和widthPixels:
public int heightPixels The absolute height of the display in pixels. public int widthPixels The absolute width of the display in pixels.参考: 1.Display中getHeight()和getWidth() 官方废弃 2.How to get Screen metrics outside an Activity?
在悬浮窗的view类里添加
@Override public boolean dispatchKeyEvent(KeyEvent event) { switch (event.getKeyCode()) { case KeyEvent.KEYCODE_BACK: MyWindowManager.removeBigWindow(getContext()); // MyWindowManager.createSmallWindow(getContext()); return true; default: return super.dispatchKeyEvent(event); } }在悬浮窗的view类里添加
@Override public boolean dispatchTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); Rect rect = new Rect(); //getGlobalVisibleRect方法的作用是获取视图在屏幕坐标中的可视区域; //getLocalVisibleRect的作用是获取视图本身可见的坐标区域,坐标以自己的左上角为原点(0,0) this.getGlobalVisibleRect(rect); if ( ! rect.contains(x, y) ) { MyWindowManager.removeBigWindow(getContext()); // MyWindowManager.createSmallWindow(getContext()); } return super.dispatchTouchEvent(event); }参考:
Android悬浮窗实现 使用WindowManagergetGlobalVisibleRect和getLocalVisibleRect参考:Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果
1.原生android自带的跑马灯效果,直接配置TextView属性
android:singleLine="true" android:ellipsize="marquee" android:focusable="true" android:focusableInTouchMode="true" android:marqueeRepeatLimit="marquee_forever" android:singleLine=true 表示使用单行文字,多行文字也就无所谓使用Marquee效果了。android:marqueeRepeatLimit,设置走马灯滚动的次数。marquee_forever为无限循环。android:ellipsize,设置了文字过长时如何切断文字,可以有none, start,middle, end, 如果使用走马灯效果则设为marquee.android:focusable,Android的缺省行为是在控件获得Focus时才会显示走马灯效果2015-9-3
0:默认为无论是否存在相同的 PendingIntent 对象都会创建一个新的 PendingIntent。
FLAG_CANCEL_CURRENT: 如果当前系统中已经存在一个相同的 PendingIntent 对象,那么就将先将已有的 PendingIntent 取消,然后重新生成一个 PendingIntent 对象。
FLAG_NO_CREATE: 如果当前系统中不存在相同的 PendingIntent 对象,系统将不会创建该 PendingIntent 对象而是直接返回 null。
FLAG_ONE_SHOT: 该 PendingIntent 只作用一次,如果该 PendingIntent 对象已经触发过一次,那么下次再获取该 PendingIntent 并且再触发时,系统将会返回一个 SendIntentException,在使用这个标志的时候一定要注意哦。
FLAG_UPDATE_CURRENT: 如果系统中已存在该 PendingIntent 对象,那么系统将保留该 PendingIntent 对象,但是会使用新的 Intent 来更新之前 PendingIntent 中的 Intent 对象数据,例如更新 Intent 中的 Extras。这个非常有用,例如之前提到的,我们需要在每次更新之后更新 Intent 中的 Extras 数据,达到在不同时机传递给 MainActivity 不同的参数,实现不同的效果。
参考: 1.What happens if you set the flag on a PendingIntent to 0? 2.Android PendingIntent 的一些小迷惑
2015-9-5
在.xml中给添加属性:
android:clickable="true" android:focusable="true" android:focusableInTouchMode="true"1.下载音标字体,如TOPhonetic.ttf,将音标字体文件放在assets/font目录下 2.为TextView设置属性
Typeface mFace = Typeface.createFromAsset(getAssets(), "font/TOPhonetic.ttf"); wordPhoneticTextView.setTypeface(mFace);若显示getAssets() is undefined,则改为context.getAssets() 参考:Android如何显示音标
2015-9-6
悬浮窗里只用了一个TextView,便于显示跑马灯效果。为了显示音标使用了TOPhonetic字体,但这样显示的英文和中文都不太喜欢,想只有音标使用TOPhonetic字体,单词和释义跟随手机当前字体。
如:
mTextView.setTextView(Html.fromHtml("<font color='red'><b>" + "红色字体" + "</b></font>TextView学习显示不同颜色"));Textview并不支持所有的html标签。如果更复杂的,可以直接使用webview组件。 查找资料有人说<font face="verdana" color="green">This is some text!</font>,想要更改字体必须手机上安装了此字体。(未测试是否可行,但直接使用的确无效)
参考: 1.HTML in TextViews 2.Android 字体设置注意的地方
1.CustomTypefaceSpan Class:
import android.graphics.Paint; import android.graphics.Typeface; import android.text.TextPaint; import android.text.style.TypefaceSpan; public class CustomTypefaceSpan extends TypefaceSpan { private final Typeface newType; public CustomTypefaceSpan(String family, Typeface type) { super(family); newType = type; } @Override public void updateDrawState(TextPaint tp) { applyCustomTypeFace(tp, newType); } @Override public void updateMeasureState(TextPaint paint) { applyCustomTypeFace(paint, newType); } private static void applyCustomTypeFace(Paint paint, Typeface tf) { int oldStyle; Typeface old = paint.getTypeface(); if (old == null) { oldStyle = 0; } else { oldStyle = old.getStyle(); } int fake = oldStyle & ~tf.getStyle(); if ((fake & Typeface.BOLD) != 0) { paint.setFakeBoldText(true); } if ((fake & Typeface.ITALIC) != 0) { paint.setTextSkewX(-0.25f); } paint.setTypeface(tf); } }2.使用方法
Typeface font1 = Typeface.createFromAsset(getAssets(), "font/font1.ttf"); Typeface font2 = Typeface.createFromAsset(getAssets(), "font/font2.ttf"); String str = "abcdefghijk"; int len = str.length(); SpannableStringBuilder ss = new SpannableStringBuilder(str); ss.setSpan (new CustomTypefaceSpan("", font1), 0, 2,Spanned.SPAN_EXCLUSIVE_INCLUSIVE); ss.setSpan (new CustomTypefaceSpan("", font2), 2, 5,Spanned.SPAN_EXCLUSIVE_INCLUSIVE); ss.setSpan (new CustomTypefaceSpan("", Typeface.DEFAULT), 5, len,Spanned.SPAN_EXCLUSIVE_INCLUSIVE); textview.setText(ss);注意: setSpan(Object what, int start, int end, int flags)的参数: 字符的位置从0开始计数
int start:开始位置(以0为基数)int end:从start开始一共(end-start)个字符,即实际结束位置为(end-1)int flags:有2个值SPAN_EXCLUSIVE_INCLUSIVE和SPAN_EXCLUSIVE_EXCLUSIVE,使用中没发现有什么区别。O.O参考: 1.android中用Spannable在TextView中设置超链接、颜色、字体 2.How set Spannable object font with custom font
可以通过tickerText.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, tickerText.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);设置加粗或斜体,但用自定义CustomTypefaceSpan时无效。未找到解决办法。 最后发现,其实手机是可以直接显示音标的。。(直接复制音标比如setTicker("[əˈsɪst]"),手机上可以正常显示!!!) 看来是源数据的编码问题@_@ 又找了几份单词excel文件,终于找到个可以正常显示的了。下载单词音标释义版excel 换了数据后,一切正常显示。。也不用替换字体了~
记得之前有做笔记的,但是竟然找不到了。。 以前在实习公司用过Navicat,但是启动特别缓慢(现在看来是电脑问题—_—),加上网上也有人说Navicat臃肿然后推荐了一堆别的软件,今天之前都用的SQLiteStudio,实话说,不太好用。刚刚新的excel文件虽然在excel里音标是正常的,但导出到txt音标又乱码了,更换字体无效!!!于是只好安装Navicat,可以直接导入excel数据,发现在我的电脑并不卡—_—果断弃用SQLiteStudio。所以这么曲折都是为什么。。。
2015-9-7
新增时间字段,类型选择TEXT,在下方默认栏里写(datetime('now','localtime')) (注意最外要有括号),即可自动添加当前时区的时间。若默认为CURRENT_TIMESTAMP,则时区为GMT。 参考: 1.sqlite database default time value ‘now’ 2.Sqlite: CURRENT_TIMESTAMP is in GMT, not the timezone of the machine 3.How to enter function values in tables ?
1.在“悬浮窗单词”SmallFloatWindowView中,一个Button用来播放单词读音,一个TextView显示单词。 最初是对“悬浮窗单词”的SmallFloatWindowView进行onTouchEvent监听,对Button进行OnClickListener监听,但这样Button无法移动,Textview可以移动。 改为:button.setOnTouchListener监听onTouch行为,在onTouch里执行button的操作。 2.虽然解决了问题,但2个监听有很多重复代码,只是处理点击有所不同。 将两者都设置setOnTouchListener,复写onTouch
@Override public boolean onTouch(View v, MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: xInView = ev.getX(); yInView = ev.getY(); xDownInScreen = ev.getRawX(); yDownInScreen = ev.getRawY(); xInScreen = ev.getRawX(); yInScreen = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: xInScreen = ev.getRawX(); yInScreen = ev.getRawY(); updateViewPosition(); break; case MotionEvent.ACTION_UP: if (xDownInScreen == xInScreen && yDownInScreen == yInScreen) { switch (v.getId()) { case R.id.small_float_window_layout: Log.d(VIEW_LOG_TAG, "点击了float window"); break; case R.id.small_float_window_play_btn: Log.d(VIEW_LOG_TAG, "点击了btn"); break; default: break; } } break; default: break; } return true; }注意:最后要返回true,否则只能监听到btn的行为。 参考:MotionEvent.ACTION_UP on Textview
2015-9-8
在ViewHolder里配置: 1.setOnClickListener
public ViewHolder( View v ) { super(v); this.view = v; v.setOnLongClickListener(this); }2.在OnLongClickListener创建PopupMenu
@Override public boolean onLongClick(View v) { if ( v == this.view ) { PopupMenu popMenu = new PopupMenu(v.getContext(), v); popMenu.inflate(R.menu.wordbook_context_menu); popMenu.setOnMenuItemClickListener(this); popMenu.show(); } return false; }3.在OnMenuItemClickListener给menu items添加操作
@Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.wordbook_context_menu_edit: Toast.makeText(view.getContext(),tvWord.getText().toString()+item.getTitle(), Toast.LENGTH_SHORT).show(); break; case R.id.wordbook_context_menu_delete: Toast.makeText(view.getContext(),tvWord.getText().toString()+item.getTitle(), Toast.LENGTH_SHORT).show(); break; case R.id.wordbook_context_menu_addto: Toast.makeText(view.getContext(),tvWord.getText().toString()+item.getTitle(), Toast.LENGTH_SHORT).show(); break; default: break; } return true; }参考:Adding context menus to RecyclerView items
2015-9-9
参考:How to select all tables names instead android_metadata android SQLite
参考:RecyclerView使用介绍
参考:Android的AlertDialog详解
2015-9-10
参考: 1.RecyclerView使用介绍 2.Easy Implementation of RecyclerView custom onItemClickListener
参考: 1.SnappingRecyclerView.java 2.Snappy scrolling in RecyclerView
在Adapter里 1.设置viewType
public static final int TYPE_VIEW_VERTICAL = 0; public static final int TYPE_VIEW_HORIZON = 1; @Override public int getItemViewType(int position) { //也可以根据item的position设置不同的viewType return viewType; } public void setItemViewType(int viewType) { this.viewType = viewType; }2.一个item布局对应一个ViewHolder 自定义一个BaseViewHolder,让所有的ViewHolder继承它。
public class BaseViewHolder extends RecyclerView.ViewHolder { public TextView tvWord; public TextView tvPhonetic; public TextView tvDefinition; public BaseViewHolder(View v) { super(v); } } public class VerticalViewHolder extends BaseViewHolder { public ImageButton imgBtn; public VerticalViewHolder( View v) { super(v); tvWord = (TextView) v.findViewById(R.id.wordcard_vertical_tv_word); tvPhonetic = (TextView) v.findViewById(R.id.wordcard_vertical_tv_phonetic); tvDefinition = (TextView) v.findViewById(R.id.wordcard_vertical_tv_definition); imgBtn = (ImageButton) v.findViewById(R.id.wordcard_vertical_imgbtn); } } public class HorizonViewHolder extends BaseViewHolder { public HorizonViewHolder( View v) { super(v); tvWord = (TextView) v.findViewById(R.id.wordcard_horizon_tv_word); tvPhonetic = (TextView) v.findViewById(R.id.wordcard_horizon_tv_phonetic); tvDefinition = (TextView) v.findViewById(R.id.wordcard_horizon_tv_definition); } }3.根据viewType执行不同的操作
public class WordRecyclerViewAdapter extends RecyclerView.Adapter<WordRecyclerViewAdapter.**BaseViewHolder**> { …… @Override public **BaseViewHolder** onCreateViewHolder( ViewGroup parent, int **viewType** ) { final **BaseViewHolder** viewHolder; View v ; switch (**viewType**) { case TYPE_VIEW_HORIZON: v = LayoutInflater.from(parent.getContext()).inflate(R.layout.wordbook_item_horizontal_cardview, parent, false); viewHolder = new HorizonViewHolder(v); break; case TYPE_VIEW_VERTICAL: default: v = LayoutInflater.from(parent.getContext()).inflate(R.layout.wordbook_item_vertical_cardview, parent, false); viewHolder = new VerticalViewHolder(v); break; } return viewHolder; } @Override public void onBindViewHolder( **BaseViewHolder** baseViewHolder, int position ) { WordCls wordCls = wordsList.get(position); baseViewHolder.itemView.setTag(wordCls); baseViewHolder.tvWord.setText(wordCls.getWord()); baseViewHolder.tvPhonetic.setText(wordCls.getPhonetic()); baseViewHolder.tvDefinition.setText(wordCls.getDefinition()); switch (**baseViewHolder.getItemViewType()**) { case TYPE_VIEW_HORIZON: HorizonViewHolder horizonViewHolder = (HorizonViewHolder) baseViewHolder; …… break; case TYPE_VIEW_VERTICAL: default: VerticalViewHolder verticalViewHolder = (VerticalViewHolder) baseViewHolder; verticalViewHolder.imgBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { } }); …… break; } } }参考: 1.How to Change the viewType of a RecyclerView item onClick 2.Recyclerview and handling different type of row inflation
2015-9-11
1.scrollby
private int mScrollY; private int mScrollYState; //保存 private RecyclerView.OnScrollListener mTotalScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); mScrollY += dy; } }; //恢复 mScrollYState = mScrollY; mRecyclerView.scrollBy(0, mScrollYState);参考:Refreshing data in RecyclerView and keeping its scroll position
2.onSaveInstanceState
Parcelable recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();//保存 recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);//恢复直接存储RecyclerView的InstanceState,但只能在fragment的生命周期里使用 参考:
1.Maintain/Save/Restore scroll position when returning to a ListView2.Retain & restore recycler view scroll position2015-9-15 3.#保存和恢复RecyclerView(Scroll)的精确滑动位置——改进版 尝试了好几天,终于想到了一种比较好的方式,能保存精确位置至本地,随意切换列表数据也能恢复相应表的浏览位置,不过还是有一点点缺陷,第一页的item会跳动一下。
scrollToPosition是根据你的操作方向来判断目标item是显示在顶端还是显示在底端的,手指上滑则显示在底端,手指下滑则显示在顶端。那么在切换列表时,则是根据现在的currentPosition和scrollToPosition(position)的position来判断滑动方向的。这样需要记录顶端和底端的2个偏移量。
切换列表时,是先计算好要滑动的位置,才会显示视图的,所以只能根据当前列表(切换前显示的列表)的视图来计算位置,切换后才会滑到正确的位置。即scrollToPosition会滑到顶端还是底端要靠你自己判断的。
RecyclerView的界面还未出现时,比如第一次打开fragment或者切换到新的数据列表时,findFirstVisibleItemPosition的值是-1,这时是无法获得child View的。调试时发现,只有当手机上能看到RecyclerView时,才能获取child view。并且getChildAt(int index)中的index是指child在当前RecyclerView视图中显示的item个数中的index,而不是针对整个dataset的,故只能获取到正在显示的某一个child的View。
若切换时currentFirstVisiblePosition < savedFirstVisiblePosition,即RecyclerView需要将列表往上拉(手指上滑)以显示下面position较大的部分,scrollToPosition会显示在底端。 但是当savedFirstVisiblePosition在其dataset中的位置在手机中显示是在第一页时,scrollToPosition是无法滑动到底端的。我想到的是scrollToPosition后,让列表scrollBy(0, recyclerView.getHeight());往上滑一部分,再scrollToPosition();回来,这样目标item就会显示在顶端了,再用smoothScrollBy(0, dyTop);就可以了。这里最后一步没有用scrollBy是因为没有效,我也不知道为什么,可能不支持连续滑动多次吧,也可以将scrollBy放在handler里,效果和smoothScrollBy差不多;但是这2种方法都会出现跳动的动画。
private void saveRecyclerViewPosition(String tableName) { firstVisibleItemPosition = ((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstVisibleItemPosition(); savedFirstVisibleChild = recyclerView.getChildAt(0); //dy正,手指将列表往上拉 //dy负,手指将列表往下拉 dyTop = savedFirstVisibleChild.getHeight() - savedFirstVisibleChild.getBottom(); dyBottom = recyclerView.getHeight() - savedFirstVisibleChild.getBottom(); prefEditorSettings.putInt(KEY_RECYCLERVIEW_SCROLL_POSITION + tableName, firstVisibleItemPosition); prefEditorSettings.putInt(KEY_RECYCLERVIEW_SCROLL_DY_TOP + tableName, dyTop); prefEditorSettings.putInt(KEY_RECYCLERVIEW_SCROLL_DY_BOTTOM + tableName, dyBottom); prefEditorSettings.commit(); } private void restoreRecyclerViewPosition(String tableName) { if ( recyclerView != null) { savedFirstVisiblePosition = prefSettings.getInt(KEY_RECYCLERVIEW_SCROLL_POSITION + tableName, 0); dyTop = prefSettings.getInt(KEY_RECYCLERVIEW_SCROLL_DY_TOP + tableName, 0); dyBottom = prefSettings.getInt(KEY_RECYCLERVIEW_SCROLL_DY_BOTTOM + tableName, 0); currentFirstVisiblePosition = ((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstVisibleItemPosition(); recyclerView.scrollToPosition(savedFirstVisiblePosition); if(currentFirstVisiblePosition > -1) { if (currentFirstVisiblePosition >= savedFirstVisiblePosition) { //savedFirstVisiblePosition在顶部 recyclerView.scrollBy(0, dyTop); } else if (currentFirstVisiblePosition < savedFirstVisiblePosition){ //savedFirstVisiblePosition在底部 if (savedFirstVisiblePosition > 4) recyclerView.scrollBy(0, dyBottom); else { //第一页的item用handler/smoothScrollBy会有跳转动作显示,暂时会找到合适的办法 recyclerView.scrollBy(0, recyclerView.getHeight()); recyclerView.scrollToPosition(savedFirstVisiblePosition); recyclerView.smoothScrollBy(0, dyTop); } } } else { //第一次打开,还未出现界面 recyclerView.scrollToPosition(savedFirstVisiblePosition); recyclerView.scrollBy(0, dyTop); } } }这个方法不受生命周期的限制,但在上述情况中视觉体验不太好,所以可以跟方法2结合起来使用。
private void saveRecyclerViewPosition(String tableName) { recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState(); hmRecyclerViewState.put(tableName, recyclerViewState); firstVisibleItemPosition = ((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstVisibleItemPosition(); savedFirstVisibleChild = recyclerView.getChildAt(0); //dy正,手指将列表往上拉 //dy负,手指将列表往下拉 dyTop = savedFirstVisibleChild.getHeight() - savedFirstVisibleChild.getBottom(); dyBottom = recyclerView.getHeight() - savedFirstVisibleChild.getBottom(); prefEditorSettings.putInt(KEY_RECYCLERVIEW_SCROLL_POSITION + tableName, firstVisibleItemPosition); prefEditorSettings.putInt(KEY_RECYCLERVIEW_SCROLL_DY_TOP + tableName, dyTop); prefEditorSettings.putInt(KEY_RECYCLERVIEW_SCROLL_DY_BOTTOM + tableName, dyBottom); prefEditorSettings.commit(); } private void restoreRecyclerViewPosition(String tableName) { recyclerViewState = hmRecyclerViewState.get(tableName); if (recyclerViewState != null) { recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState); } else { if ( recyclerView != null) { savedFirstVisiblePosition = prefSettings.getInt(KEY_RECYCLERVIEW_SCROLL_POSITION + tableName, 0); dyTop = prefSettings.getInt(KEY_RECYCLERVIEW_SCROLL_DY_TOP + tableName, 0); dyBottom = prefSettings.getInt(KEY_RECYCLERVIEW_SCROLL_DY_BOTTOM + tableName, 0); currentFirstVisiblePosition = ((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstVisibleItemPosition(); recyclerView.scrollToPosition(savedFirstVisiblePosition); if(currentFirstVisiblePosition > -1) { if (currentFirstVisiblePosition >= savedFirstVisiblePosition) { //savedFirstVisiblePosition在顶部 recyclerView.scrollBy(0, dyTop); } else if (currentFirstVisiblePosition < savedFirstVisiblePosition){ //savedFirstVisiblePosition在底部 if (savedFirstVisiblePosition > 4) recyclerView.scrollBy(0, dyBottom); else { //第一页的item用handler/smoothScrollBy会有跳转动作显示,暂时会找到合适的办法 recyclerView.scrollBy(0, recyclerView.getHeight()); recyclerView.scrollToPosition(savedFirstVisiblePosition); recyclerView.smoothScrollBy(0, dyTop); } } } else { //第一次打开,还未出现界面 recyclerView.scrollToPosition(savedFirstVisiblePosition); recyclerView.scrollBy(0, dyTop); } } } }避免每次切换fragment时都重新加载view。 注意:一个activity只有一个actionbar。
1.SlidingmenuFragment
@Override public void onListItemClick(ListView lv, View v, int position, long id) { newContentFragment = null; object = lv.getItemAtPosition(position); str=(String)object; getActivity().setTitle(str); actionBar.setDisplayShowCustomEnabled(false); if (str.matches(getResources().getString(R.string.center))){ if (centerFragment == null) centerFragment = new CenterFragment(); newContentFragment = centerFragment; } else if (str.matches(getResources().getString(R.string.home))){ if (homeFragment == null) homeFragment = new HomeFragment(getActivity()); newContentFragment = homeFragment; } else if (str.matches(getResources().getString(R.string.wordbook))){ if (wordBookFragment == null) wordBookFragment = new WordBookFragment(); newContentFragment = wordBookFragment; //加载自定义actionbar布局,在wordBookFragment里实现actionBar.setCustomView()后,在这里设置为true即可。 actionBar.setDisplayShowCustomEnabled(true); } else if (str.matches(getResources().getString(R.string.BBS))){ if (bbsFragment == null) bbsFragment = new BBSFragment(); newContentFragment = bbsFragment; } else if (str.matches(getResources().getString(R.string.settings))){ if (settingsFragment == null) settingsFragment = new SettingsFragment(); newContentFragment = settingsFragment; } if (newContentFragment != null){ switchFragment(newContentFragment); } } private void switchFragment(Fragment fragment) { if (getActivity() == null) return; if (getActivity() instanceof MainActivity) { MainActivity main = (MainActivity) getActivity(); main.switchContent(fragment); } }2.MainActivity
public void switchContent(Fragment newFragment) { if ( contentFragment == null || newFragment == null) return; FragmentTransaction transaction = getFragmentManager().beginTransaction(); if (contentFragment != newFragment) { if (!newFragment.isAdded()) { // 隐藏当前的fragment,add下一个到Activity中 transaction.hide(contentFragment).add(R.id.content_frame,newFragment).commit(); } else { // 隐藏当前的fragment,显示下一个 transaction.hide(contentFragment).show(newFragment).commit(); } contentFragment = newFragment; } //通过handler来避免滑动卡顿的情况 handler.post(new Runnable() { @Override public void run() { sm.showContent(); } }); }参考:
1.Android SlidingMenu Fragment的简单优化2.SlidingMenu 左侧菜单切换3.slidingmenu+fragment实现常用的侧滑效果(包括Fragment状态的保存)4.SlidingMenu切换fragment卡顿问题2015-9-16
本来想用金山的,但一直没收到key,发现扇贝的查单词不用key,就用了扇贝的API。 1.在AsyncTask里联网并解析数据
class ParseJsonTask extends AsyncTask<String, Void, Boolean> { HorizonViewHolder horizonViewHolder; WordCls wordCls; int position; String definitionEN; String definitionCN; String audioUrlUS; Handler handler; //用来传值 public ParseJsonTask(HorizonViewHolder horizonViewHolder, WordCls wordCls, int position, Handler handler) { super(); this.horizonViewHolder = horizonViewHolder; this.wordCls = wordCls; this.position = position; this.handler = handler; } @Override protected void onPreExecute() { horizonViewHolder.progressBar.setVisibility(View.VISIBLE); super.onPreExecute(); } @Override protected void onPostExecute(Boolean result) { Message msg = handler.obtainMessage(); horizonViewHolder.progressBar.setVisibility(View.INVISIBLE); if (result) { wordCls.setDefinitionEN(definitionEN); wordCls.setDefinitionCN(definitionCN); wordCls.setAudioUrlUS(audioUrlUS); wordCls.setLoaded(true); WordsManager.addWordLoadInfo(tableName, wordCls); updateItem(position, wordCls); msg.what = position; }else { msg.what = 0; Log.i(wordCls.getWord(), "获取数据失败"); } handler.sendMessage(msg); super.onPostExecute(result); } @Override protected Boolean doInBackground(String... params) { String path = "https://api.shanbay.com/bdc/search/?word=" + wordCls.getWord(); try { URL url = new URL(path); Source source = new Source(url.openConnection()); //jericho-html-3.1.jar String jsonstr = source.toString(); JSONObject jsonObj = new JSONObject(jsonstr); JSONObject data = jsonObj.getJSONObject("data"); JSONObject defEN = data.getJSONObject("en_definition"); definitionEN = defEN.getString("pos") + "." + defEN.getString("defn"); JSONObject defCN = data.getJSONObject("cn_definition"); definitionCN = defCN.getString("pos") + defCN.getString("defn"); audioUrlUS = data.getString("us_audio"); return true; } catch (Exception e) { Toast.makeText(mContext, "获取数据失败", Toast.LENGTH_SHORT).show(); return false; } } } if ( ! wordCls.isLoaded() ) { Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { 根据AsyncTask的执行结果传递的值来判断下一步操作 if (msg.what == position) horizonViewHolder.tvHint.setVisibility(View.INVISIBLE); else horizonViewHolder.tvHint.setText("数据获取失败,请重试"); } }; ParseJsonTask parseJsonTask = new ParseJsonTask(horizonViewHolder, wordCls, position, handler); parseJsonTask.execute(); }参考:
1.Json方式获取网络数据2.Android 之 网络访问服务器,解析JSON数据3.【Qt编程】基于Qt的词典开发系列<九>—JSON数据解析4.android两种方式获取AsyncTask返回值2015-9-17
之前继承的SnappingRecyclerView,水平状态的时候,点击item里的TextView更新会出现错位,有时还会跑到第一个位置去,并且感觉他的有点复杂。。 然后发现自己写一个实现也并不难@_@ 我的每一个item都是全屏的卡片,所以这样就可以了。
private void scrollToCenter(RecyclerView recyclerView) { firstVisibleItemPosition = ((LinearLayoutManager)recyclerView.getLayoutManager()).findFirstVisibleItemPosition(); lastVisibleItemPosition = ((LinearLayoutManager)recyclerView.getLayoutManager()).findLastVisibleItemPosition(); if ( firstVisibleItemPosition < lastVisibleItemPosition ) { recyclerViewWidth = recyclerView.getWidth(); firstVisibleChild = recyclerView.getChildAt(0); firstChildVisibleWidth = firstVisibleChild.getRight(); if ( firstChildVisibleWidth > ( recyclerViewWidth / 2 ) ) recyclerView.smoothScrollToPosition(firstVisibleItemPosition); else if ( firstChildVisibleWidth < ( recyclerViewWidth / 2 ) ) recyclerView.smoothScrollToPosition(lastVisibleItemPosition); } } @Override public void onScrollStateChanged(final RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); switch (newState) { case RecyclerView.SCROLL_STATE_IDLE: switch (wordCardAdapter.getItemViewType()) { case TYPE_VIEW_HORIZON: scrollToCenter(recyclerView); break; case TYPE_VIEW_VERTICAL: default: break; } break; case RecyclerView.SCROLL_STATE_DRAGGING: break; case RecyclerView.SCROLL_STATE_SETTLING: break; } }参考:
1.android 下拉菜单实现详解 PopupWindow2015-9-18
自定义MyLayoutManager
/* * Copyright 2015 serso aka se.solovyev * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Contact details * * Email: se.solovyev@gmail.com * Site: http://se.solovyev.org */ package org.solovyev.android.views.llm; import android.content.Context; import android.graphics.Rect; import android.support.v4.view.ViewCompat; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import java.lang.reflect.Field; /** * {@link android.support.v7.widget.LinearLayoutManager} which wraps its content. Note that this class will always * wrap the content regardless of {@link android.support.v7.widget.RecyclerView} layout parameters. * <p/> * Now it's impossible to run add/remove animations with child views which have arbitrary dimensions (height for * VERTICAL orientation and width for HORIZONTAL). However if child views have fixed dimensions * {@link #setChildSize(int)} method might be used to let the layout manager know how big they are going to be. * If animations are not used at all then a normal measuring procedure will run and child views will be measured during * the measure pass. */ public class LinearLayoutManager extends android.support.v7.widget.LinearLayoutManager { private static boolean canMakeInsetsDirty = true; private static Field insetsDirtyField = null; private static final int CHILD_WIDTH = 0; private static final int CHILD_HEIGHT = 1; private static final int DEFAULT_CHILD_SIZE = 100; private final int[] childDimensions = new int[2]; private final RecyclerView view; private int childSize = DEFAULT_CHILD_SIZE; private boolean hasChildSize; private int overScrollMode = ViewCompat.OVER_SCROLL_ALWAYS; private final Rect tmpRect = new Rect(); @SuppressWarnings("UnusedDeclaration") public LinearLayoutManager(Context context) { super(context); this.view = null; } @SuppressWarnings("UnusedDeclaration") public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); this.view = null; } @SuppressWarnings("UnusedDeclaration") public LinearLayoutManager(RecyclerView view) { super(view.getContext()); this.view = view; this.overScrollMode = ViewCompat.getOverScrollMode(view); } @SuppressWarnings("UnusedDeclaration") public LinearLayoutManager(RecyclerView view, int orientation, boolean reverseLayout) { super(view.getContext(), orientation, reverseLayout); this.view = view; this.overScrollMode = ViewCompat.getOverScrollMode(view); } public void setOverScrollMode(int overScrollMode) { if (overScrollMode < ViewCompat.OVER_SCROLL_ALWAYS || overScrollMode > ViewCompat.OVER_SCROLL_NEVER) throw new IllegalArgumentException("Unknown overscroll mode: " + overScrollMode); if (this.view == null) throw new IllegalStateException("view == null"); this.overScrollMode = overScrollMode; ViewCompat.setOverScrollMode(view, overScrollMode); } public static int makeUnspecifiedSpec() { return View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); } @Override public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) { final int widthMode = View.MeasureSpec.getMode(widthSpec); final int heightMode = View.MeasureSpec.getMode(heightSpec); final int widthSize = View.MeasureSpec.getSize(widthSpec); final int heightSize = View.MeasureSpec.getSize(heightSpec); final boolean hasWidthSize = widthMode != View.MeasureSpec.UNSPECIFIED; final boolean hasHeightSize = heightMode != View.MeasureSpec.UNSPECIFIED; final boolean exactWidth = widthMode == View.MeasureSpec.EXACTLY; final boolean exactHeight = heightMode == View.MeasureSpec.EXACTLY; final int unspecified = makeUnspecifiedSpec(); if (exactWidth && exactHeight) { // in case of exact calculations for both dimensions let's use default "onMeasure" implementation super.onMeasure(recycler, state, widthSpec, heightSpec); return; } final boolean vertical = getOrientation() == VERTICAL; initChildDimensions(widthSize, heightSize, vertical); int width = 0; int height = 0; // it's possible to get scrap views in recycler which are bound to old (invalid) adapter entities. This // happens because their invalidation happens after "onMeasure" method. As a workaround let's clear the // recycler now (it should not cause any performance issues while scrolling as "onMeasure" is never // called whiles scrolling) recycler.clear(); final int stateItemCount = state.getItemCount(); final int adapterItemCount = getItemCount(); // adapter always contains actual data while state might contain old data (f.e. data before the animation is // done). As we want to measure the view with actual data we must use data from the adapter and not from the // state for (int i = 0; i < adapterItemCount; i++) { if (vertical) { if (!hasChildSize) { if (i < stateItemCount) { // we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items // we will use previously calculated dimensions measureChild(recycler, i, widthSize, unspecified, childDimensions); } else { logMeasureWarning(i); } } height += childDimensions[CHILD_HEIGHT]; if (i == 0) { width = childDimensions[CHILD_WIDTH]; } if (hasHeightSize && height >= heightSize) { break; } } else { if (!hasChildSize) { if (i < stateItemCount) { // we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items // we will use previously calculated dimensions measureChild(recycler, i, unspecified, heightSize, childDimensions); } else { logMeasureWarning(i); } } width += childDimensions[CHILD_WIDTH]; if (i == 0) { height = childDimensions[CHILD_HEIGHT]; } if (hasWidthSize && width >= widthSize) { break; } } } if (exactWidth) { width = widthSize; } else { width += getPaddingLeft() + getPaddingRight(); if (hasWidthSize) { width = Math.min(width, widthSize); } } if (exactHeight) { height = heightSize; } else { height += getPaddingTop() + getPaddingBottom(); if (hasHeightSize) { height = Math.min(height, heightSize); } } setMeasuredDimension(width, height); if (view != null && overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS) { final boolean fit = (vertical && (!hasHeightSize || height < heightSize)) || (!vertical && (!hasWidthSize || width < widthSize)); ViewCompat.setOverScrollMode(view, fit ? ViewCompat.OVER_SCROLL_NEVER : ViewCompat.OVER_SCROLL_ALWAYS); } } private void logMeasureWarning(int child) { if (BuildConfig.DEBUG) { Log.w("LinearLayoutManager", "Can't measure child #" + child + ", previously used dimensions will be reused." + "To remove this message either use #setChildSize() method or don't run RecyclerView animations"); } } private void initChildDimensions(int width, int height, boolean vertical) { if (childDimensions[CHILD_WIDTH] != 0 || childDimensions[CHILD_HEIGHT] != 0) { // already initialized, skipping return; } if (vertical) { childDimensions[CHILD_WIDTH] = width; childDimensions[CHILD_HEIGHT] = childSize; } else { childDimensions[CHILD_WIDTH] = childSize; childDimensions[CHILD_HEIGHT] = height; } } @Override public void setOrientation(int orientation) { // might be called before the constructor of this class is called //noinspection ConstantConditions if (childDimensions != null) { if (getOrientation() != orientation) { childDimensions[CHILD_WIDTH] = 0; childDimensions[CHILD_HEIGHT] = 0; } } super.setOrientation(orientation); } public void clearChildSize() { hasChildSize = false; setChildSize(DEFAULT_CHILD_SIZE); } public void setChildSize(int childSize) { hasChildSize = true; if (this.childSize != childSize) { this.childSize = childSize; requestLayout(); } } private void measureChild(RecyclerView.Recycler recycler, int position, int widthSize, int heightSize, int[] dimensions) { final View child; try { child = recycler.getViewForPosition(position); } catch (IndexOutOfBoundsException e) { if (BuildConfig.DEBUG) { Log.w("LinearLayoutManager", "LinearLayoutManager doesn't work well with animations. Consider switching them off", e); } return; } final RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) child.getLayoutParams(); final int hPadding = getPaddingLeft() + getPaddingRight(); final int vPadding = getPaddingTop() + getPaddingBottom(); final int hMargin = p.leftMargin + p.rightMargin; final int vMargin = p.topMargin + p.bottomMargin; // we must make insets dirty in order calculateItemDecorationsForChild to work makeInsetsDirty(p); // this method should be called before any getXxxDecorationXxx() methods calculateItemDecorationsForChild(child, tmpRect); final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child); final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child); final int childWidthSpec = getChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally()); final int childHeightSpec = getChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.height, canScrollVertically()); child.measure(childWidthSpec, childHeightSpec); dimensions[CHILD_WIDTH] = getDecoratedMeasuredWidth(child) + p.leftMargin + p.rightMargin; dimensions[CHILD_HEIGHT] = getDecoratedMeasuredHeight(child) + p.bottomMargin + p.topMargin; // as view is recycled let's not keep old measured values makeInsetsDirty(p); recycler.recycleView(child); } private static void makeInsetsDirty(RecyclerView.LayoutParams p) { if (!canMakeInsetsDirty) { return; } try { if (insetsDirtyField == null) { insetsDirtyField = RecyclerView.LayoutParams.class.getDeclaredField("mInsetsDirty"); insetsDirtyField.setAccessible(true); } insetsDirtyField.set(p, true); } catch (NoSuchFieldException e) { onMakeInsertDirtyFailed(); } catch (IllegalAccessException e) { onMakeInsertDirtyFailed(); } } private static void onMakeInsertDirtyFailed() { canMakeInsetsDirty = false; if (BuildConfig.DEBUG) { Log.w("LinearLayoutManager", "Can't make LayoutParams insets dirty, decorations measurements might be incorrect"); } } }参考:RecyclerView高度随Item自适应
2015-9-19
在ListView下面有一个Button,ListView设置高度为wrap_content,当ListView高度超出屏幕时,Button就不显示了。 解决: 给ListView增加
android:layout_weight="1"2015-9-21
参考:How do I close a SearchView programmatically?
参考:how to make searchview loose focus and collapse when clicked elsewhere on activity
2015-9-22
2015-9-24
res/values/color.xml
<color name="transparent_background">#50000000</color>#5000000前两位是透明的效果参数从00—99(透明—不怎么透明),后6位是颜色的设置
参考:android 成长 UI 学习之 Activity 透明,半透明效果的设置transparent
在drawable目录里定义一个corners_bg.xml:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/translucent_background" /> //填充颜色,这里设置的半透明 <corners android:radius="3dp" /> //圆角弧度 </shape>引用:
android:background="@drawable/corners_bg"参考:【Android】Android布局中实现圆角边框
(1)从资源文件中播放
MediaPlayer player = new MediaPlayer.create(this,R.raw.test); player.stare();(2)从文件系统播放
MediaPlayer player = new MediaPlayer(); String path = "/sdcard/test.mp3"; player.setDataSource(path); player.prepare(); player.start();(3)从网络播放
通过URI的方式:
String path="http://**************.mp3"; //音频的网络地址 Uri uri = Uri.parse(path); MediaPlayer player = new MediaPlayer.create(this,uri); player.start();通过设置数据源的方式:
MediaPlayer player = new MediaPlayer.create(); String path="http://**************.mp3"; //音频的网络地址 player.setDataSource(path); player.prepare(); player.start();参考:Android中的音频播放(MediaPlayer和SoundPool)
2.SoundPool 低延迟播放,适合播放实时音实现同时播放多个声音,如游戏中炸弹的爆炸音等小资源文件,此类音频比较适合放到资源文件夹 res/raw下和程序一起打成APK文件
SoundPool 使用音效池的概念来管理多个短促的音效;cpu资源占用量低和反应延迟小;支持自行设置的品质、音量、播放比率等参数;异步线程,占用资源少,可以同时合成多种音效;API < 17,res/values/styles.xml :
<item name="android:actionButtonStyle">@style/ActionButtonStyle</item> <style name="ActionButtonStyle" parent="@android:style/Widget.Holo.Light.ActionButton"> <item name="android:minWidth">0dip</item> <item name="android:paddingLeft">0dip</item> <item name="android:paddingRight">0dip</item> </style>API > 17,res/values-v17/styles.xml :
<item name="android:actionButtonStyle">@style/ActionButtonStyle</item> <style name="ActionButtonStyle" parent="@android:style/Widget.Holo.Light.ActionButton"> <item name="android:minWidth">0dip</item> <item name="android:paddingStart">0dip</item> <item name="android:paddingEnd">0dip</item> </style>参考:Is there a way to reduce the spacing between the Action Item Icons on Action Bar?
style.xml:
<resources xmlns:android="http://schemas.android.com/apk/res/android"> <style name="AppTheme" parent="@android:style/Theme.Holo.Light"> <item name="android:actionBarSize">30dp</item> </style> </resources>manifest.xml:
<application android:label="@string/app_name" android:theme="@style/AppTheme" android:icon="@drawable/ic_launcher" >参考:ActonBar介绍-修改actionbar的高度
输入的文字:
int id = searchView.getContext().getResources().getIdentifier("android:id/search_src_text", null, null); TextView textView = (TextView) searchView.findViewById(id); textView.setTextColor(Color.WHITE);参考:Android中SearchView修改字体颜色
hint提示文字:
searchView.setQueryHint(Html.fromHtml("<font color = #999999>" + getResources().getString(R.string.search_input) + "</font>"));参考:关于SearchView的一些小细节
使用SearchViewFormatter 用法:
new SearchViewFormatter() .setSearchBackGroundResource(R.drawable.my_bg) .setSearchIconResource(R.drawable.my_ic, true, false) //true to icon inside edittext, false to outside .setSearchVoiceIconResource(R.drawable.my_ic) .setSearchTextColorResource(R.color.my_color) .setSearchHintColorResource(R.color.my_color) .setSearchCloseIconResource(R.drawable.my_ic) .setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) .format(mSearchView);参考:Changing the cursor color in SearchView without ActionBarSherlock
在此Activity里设置
ActionBar mActionBar = getActionBar(); mActionBar.setDisplayHomeAsUpEnabled(true);//show back button在Manifest.xml文件中设置这个Activity的parentActivity
<activity android:parentActivityName=".MainActivity" android:name=".BackActionBarActivity" android:launchMode="singleTask" android:label="@string/title_activity_back_action_bar"> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value=".MainActivity"/> </activity>参考:Android ActionBar 返回上一个Activity
styles.xml:
<style name="CustomActivityTheme" parent="AppTheme"> <item name="android:homeAsUpIndicator">@drawable/custom_home_as_up_icon</item> </style>Manifest.xml:
<activity android:name="com.example.CustomActivity" android:theme="@style/CustomActivityTheme" > </activity>参考:Change the actionbar homeAsUpIndicator Programamtically
2015-9-25
使用RecyclerView-FlexibleDivider
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview); recyclerView.addItemDecoration( new HorizontalDividerItemDecoration.Builder(this) .color(Color.RED) .size(getResources().getDimensionPixelSize(R.dimen.divider)) .margin(getResources().getDimensionPixelSize(R.dimen.leftmargin), getResources().getDimensionPixelSize(R.dimen.rightmargin)) .build());参考:RecyclerView-FlexibleDivider——控制RecyclerView项目分割的Android类库
2015-9-26
将Request对象添加到RequestQueue里面。
RequestQueue mQueue = Volley.newRequestQueue(context); ImageRequest imageRequest = new ImageRequest( "http://developer.android.com/images/home/aw_dac.png", new Response.Listener<Bitmap>() { @Override public void onResponse(Bitmap response) { imageView.setImageBitmap(response); } }, 0, 0, Config.RGB_565, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { imageView.setImageResource(R.drawable.default_image); } }); mQueue.add(imageRequest);ImageRequest的构造函数接收六个参数,第一个参数就是图片的URL地址,这个没什么需要解释的。第二个参数是图片请求成功的回调,这里我们把返回的Bitmap参数设置到ImageView中。第三第四个参数分别用于指定允许图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,指定成0的话就表示不管图片有多大,都不会进行压缩。第五个参数用于指定图片的颜色属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展示最好的颜色属性,每个图片像素占据4个字节的大小,而RGB_565则表示每个图片像素占据2个字节大小。第六个参数是图片请求失败的回调,这里我们当请求失败时在ImageView中显示一张默认图片。
ImageLoader也可以用于加载网络上的图片,并且它的内部也是使用ImageRequest来实现的,不过ImageLoader明显要比ImageRequest更加高效,因为它不仅可以帮我们对图片进行缓存,还可以过滤掉重复的链接,避免重复发送请求。
创建一个RequestQueue对象。创建一个ImageLoader对象。获取一个ImageListener对象。调用ImageLoader的get()方法加载网络上的图片。
ImageLoader imageLoader = new ImageLoader(mQueue, new ImageCache() {
@Override public void putBitmap(String url, Bitmap bitmap) { } @Override public Bitmap getBitmap(String url) { return null; }}); ImageListener listener = ImageLoader.getImageListener(imageView,
R.drawable.default_image, R.drawable.failed_image);//imageLoader.get(“https://img-my.csdn.net/uploads/201404/13/1397393290_5765.jpeg“, listener); imageLoader.get(“https://img-my.csdn.net/uploads/201404/13/1397393290_5765.jpeg“, listener);
参考:Android Volley完全解析(二),使用Volley加载网络图片
自定义TextDrawable,将文字内容传入,用canvas将文字和绘制的圆角矩形合并(本地图片同理) @Override public void draw(Canvas canvas) { paint.setColor(Color.RED); rectF.set(padding, -height-linsSpaceExtra, padding+rectWidth, -linsSpaceExtra); canvas.drawRoundRect(rectF, height/2, height/2, paint); //canvas.drawRect(padding, -height-linsSpaceExtra, padding+rectWidth, -linsSpaceExtra, paint); int baseline = (int) (rectF.top + (rectF.bottom - rectF.top - paint.getFontMetrics().bottom + paint.getFontMetrics().top) / 2 - paint.getFontMetrics().top)-2; paint.setColor(Color.WHITE); canvas.drawText(text, rectF.centerX(), baseline, paint); } 参考:Textview文字末尾拼接带本地图片背景文字
2015-10-5
参考:SimpleDateFormat使用详解
2015-10-7
索引可大幅减少扫描表所需的时间。 请遵照以下准则: 索引中的列顺序会影响性能。 WHERE 子句通常使用的列应该放在前面,然后放置 ORDER BY 子句通常使用的列。 对于包含检索的数据的列,创建覆盖索引。 避免重复索引。 SQLite 数据库引擎将自动为具有 UNIQUE 或 PRIMARY KEY 限制的列创建索引。
参考:Sqlite 将一张表的数据复制到另一张表中
sqlite中是不支持删除列操作的,所以网上alter table table_name drop column col_name这个语句在sqlite中是无效的,而替代的方法可以如下:
1.根据原表创建一张新表 2.删除原表 3.将新表重名为旧表的名称
示例例子如下
1.创建一张旧表Student,包含id(主码),name, tel
create table student ( id integer primary key, name text, tel text )2.给旧表插入两个值
insert into student(id,name,tel) values(101,"Jack","110") insert into student(id,name,tel) values(102,"Rose","119")3.接下来我们删除电话这个列,首先根据student表创建一张新表teacher
create table teacher as select id,name from student可以看到tel这一列已经没有了
4.然后我们删除student这个表
drop table if exists student5.将teacher这个表重命名为student
alter table teacher rename to student结果演示:
select * from student order by name desc(desc降序, asc升序)这样就可以得到我们想要的结果了。
参考:Sqlite删除列方法
2015-10-9
参考:android里用MediaPlayer,当音乐现在正在播放时,点击按钮是如何让音乐从头播放
参考:判断sqlite数据库中表是否存在的方法
2015-10-10
在AndroidManifest.xml的需要禁止转向的Activity配置中加入android:screenOrientation="portrait"属性即可(landscape是横向,portrait是纵向) 参考:Android禁止横屏竖屏切换
2015-10-11
参考:
Android OkHttp完全解析 是时候来了解OkHttp了okhttp-utils参考:3种Java从文件路径中获取文件名的方法
2016-02-28
在低版本中设置了 CardElevation 之后 CardView 会自动留出空间供阴影显示,而 Lollipop 之后则需要手动设置 Margin 边距来预留空间。 因此,我们需要自定义一个 dimen 作为 CardView 的 Margin 值:
创建 /res/value 和 /res/value-v21 资源文件夹于项目对应 Module 目录下,前者放置旧版本/通用的资源文件(了解的可以跳过),后者放置 21 及更高 SDK 版本的资源文件。
在 value 内的 dimen.xml 创建一个 Dimension ( 属性),随便命个名(如 xxx_card_margin)并填入数值 0dp。
接着在 value-v21 文件夹内的 dimen.xml 创建名字相同的 Dimension,并填入你期望的预留边距(一般和 CardElevation 阴影大小相同)
最后,在你布局中的 CardView 中设置 android:layout_margin="@dimen/xxx_card_margin"
这样就解决了低版本中边距过大或者视觉效果不统一的问题了。
参考:关于使用 CardView 开发过程中要注意的细节
