SimpleWord 简词开发记录笔记

2015-8-6 15:07:54

词库

找不到词库,暂时找到一个带音标和释义的考研单词excel(估计是好几年前的大纲词汇),就先用这个吧。
excel不能显示音标的话,还得下载字体TOPhonetic.ttf。

数据库

excel导入SQLite
试了几个可视化工具,就SQLiyeStudio比较满意,也没有乱码。
开始时把excel另存为.csv文件,系统的分隔符是逗号,但是去控制面板改了,excel导出时还是逗号,不知道为什么,可能需要重启电脑吧,懒得重启。竟然忘记replace了!所以excel → 制表符分隔的txt → replace分隔符,就ok了^_^

优化——延迟切换fragment

public void switchContent(Fragment fragment) {
    contentFragment = fragment;
    getFragmentManager().beginTransaction().replace(R.id.content_frame, fragment).commit();
//  sm.showContent();
    Handler h = new Handler();
    h.postDelayed(new Runnable() {
        public void run() {
            sm.showContent();
        }
    }, 50);
}

2015-8-10

单词本界面想做成卡片来滑动

RecyclerView

关于引入V7包:http://wp.aesean.com/?p=185

2015-8-11

  1. 继承RecyclerView.Adapter出现The hierarchy of the type ViewHolder is inconsistent,因为菜单用的SlidingMenu,support-v4包可能不是最新的,将SlidingMenu的libs下的v4包替换成最新的就可以了。
  2. Call requires API level 21 (current min is 17): android.content.Context#getDrawable
    解决

    ContextCompat.getDrawable(this, R.drawable.your_drawable)

2015-8-12

在actionbar添加spinner下拉列表

1.actionbar.xml

  
<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>item1item>  
<item>item2item>  
<item>item3item>   
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) {}  
}  

同一个activity的不同fragment显示不同的actionbar

在单词本界面的actionbar想显示一个spinner下拉选择单词本(暂时未加入切换单词本的功能),在WordBookFragment的onCreateView()里设置自定义actionbar

getActivity().getActionBar().setDisplayShowCustomEnabled(true);

别的fragment里设置

getActivity().getActionBar().setDisplayShowCustomEnabled(false);

或者加载另外的view。

不同fragment的标题

分别在onCreateView()里设置

getActivity().setTitle("标题");

若写成

getActivity().getActionBar().setTitle("标题");    

会将所有fragment设置成同一标题

在fragment里获取actionbar

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

状态栏通知

可删除

NotificationCompat.Builder mBuilder =
    new NotificationCompat.Builder(this)
    .setSmallIcon(R.drawable.notification_icon)
    .setContentTitle("My notification")
    .setContentText("Hello World!");
// Creates an explicit intent for an Activity in your app
Intent resultIntent = new Intent(this, ResultActivity.class);
// The stack builder object will contain an artificial back stack for the
// started Activity.
// This ensures that navigating backward from the Activity leads out of
// your application to the Home screen.
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// Adds the back stack for the Intent (but not the Intent itself)
stackBuilder.addParentStack(ResultActivity.class);
// Adds the Intent that starts the Activity to the top of the stack
stackBuilder.addNextIntent(resultIntent);
PendingIntent resultPendingIntent =
        stackBuilder.getPendingIntent(
            0,
            PendingIntent.FLAG_UPDATE_CURRENT
        );
mBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager =
    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// mId allows you to update the notification later on.
mNotificationManager.notify(mId, mBuilder.build());

常驻状态栏(显示在“进行中/ongoing”)

Notification

  • 低于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前台服务

在service里

startForeground(mID, notification);     //设置前台服务

关于服务service

  • 启动service的时候,onCreate方法只有第一次会调用,onStartCommandonStart(已被淘汰)每次都被调用。onStartCommand会告诉系统如何重启服务,如判断是否异常终止后重新启动,在何种情况下异常终止。
  • 2.0 API level之后,实现onStart等同于重写onStartCommand并返回START_STICKY 。
  • 2.0 API level之后,onStart()方法被onStartCommand()取代了。
  • 必须在AndroidManifest.xml中注册

    name="完整包名.ServiceNotification" />
    

启动服务

mContext.startService(intent);

关闭服务

mContext.stopService(intent);

保存、恢复spinner的选中项

保存

onItemSelected()

int userChoice = spinner.getSelectedItemPosition();
SharedPreferences sharedPref = getSharedPreferences("FileName",0);
SharedPreferences.Editor prefEditor = sharedPref.edit();
prefEditor.putInt("userChoiceSpinner",usersChoice);
prefEditor.commit();

恢复

SharedPreferences sharedPref = getSharedPreferences("FileName",MODE_PRIVATE);
int spinnerValue = sharedPref.getInt"userChoiceSpinner",-1);
if(spinnerValue != -1) 
    spinner.setSelection(spinnerValue);

关于Activity生命周期该做的事情

onCreate()

  • 使用onCreate()初始化你的Activity:创建UI、为类的变量分配引用、绑定数据到控件、创建Service和线程。
  • 为避免快速的创建和销毁对象引发额外的垃圾回收,如果你的应用程序正常创建一套对象,建议它们在onCreate()中创建,因为在Activity的生命周期中它只被调用一次。
  • onCreate()里面尽量少做事情,避免程序启动太久都看不到界面。

onResume()

当从Paused状态恢复activity时,系统会调用onResume()方法。
系统每次调用onResume()时,activity都处于前台,包括第一次创建的时候。所以,应该实现onResume()来初始化那些在onPause方法里面释放掉的组件,并执行那些activity每次进入Resumed state都需要的初始化动作 (例如开始动画与初始化那些只有在获取用户焦点时才需要的组件)

onPause()

  • 不应使用onPause()来保存用户改变的数据 (例如填入表格中的个人信息) 到永久存储(File或者DB)上。仅仅当确认用户期待那些改变能够被自动保存的时候(例如正在撰写邮件草稿),才把那些数据存到永久存储 。
  • 避免在onPause()时执行CPU-intensive 的工作,例如写数据到DB,因为它会导致切换到下一个activity变得缓慢(应该把那些heavy-load的工作放到onStop()去做)。
  • 如果activity实际上是要被Stop,为了切换的顺畅应减少在OnPause()方法里面的工作量。
  • 停止动画或者是其他正在运行的操作,那些都会导致CPU的浪费.
  • 提交在用户离开时期待保存的内容(例如邮件草稿).
  • 释放系统资源,例如broadcast receivers, sensors (比如GPS), 或者是其他任何会影响到电量的资源。

onStop()

  • 当activity调用onStop()方法, activity不再可见,并且应该释放那些不再需要的所有资源。一旦activity停止了,系统会在需要内存空间时摧毁它的实例。极端情况下,系统会直接杀死我们的app进程,并不执行activity的onDestroy()回调方法, 因此我们需要使用onStop()来释放资源,从而避免内存泄漏。
    所以我们应该使用onStop()来执行那些耗时的释放资源的操作,例如往数据库写信息。
    -无论什么原因导致activity停止,系统总是会在onStop()之前调用onPause()方法。

onDestroy()

大多数app并不需要实现这个方法,因为局部类的references会随着activity的销毁而销毁,并且我们的activity应该在onPause()onStop()中执行清除activity资源的操作。然而,如果activity含有在onCreate调用时创建的后台线程,或者是其他有可能导致内存泄漏的资源,则应该在OnDestroy()时进行资源清理,杀死后台线程。

除非程序在onCreate()方法里面就调用了finish()方法,系统通常是在执行了onPause()onStop()之后再调用onDestroy()。在某些情况下,例如我们的activity只是做了一个临时的逻辑跳转的功能,它只是用来决定跳转到哪一个activity,这样的话,需要在onCreate里面调用finish方法,这样系统会直接调用onDestory,跳过生命周期中的其他方法。

与Activity生命周期结合的应用场景

  • 与广播(Broadcast)结合
    onResume注册广播(registerLinstener),在onPause注销广播(unregisterLinstener)。
    例如:做”摇一摇”功能(传感器)、监听网络变化,就可以在onResume中注册监听,在onPause里注销掉,已节省资源提高效率。
  • 与服务(Service)结合
    onStartCommand绑定服务(bindService),在onStop中取消绑定(unbindService)。
    例如:需要通过Service定时更新UI上的数据,而Activity的可见周期在onStartonStop之间,那么就可以再onStart时启动服务,在onStop时停止服务。为了节约系统资源,除了提高用户体验以外,开发人员应尽可能的优化程序。
  • 与Cursor结合
    使用managedQuery让Activity帮你管理Cursor的生命周期,不用自己去close。
  • 释放资源
    可以在onDestory中释放一些资源。比如可以在onDestory时调用MediaPlayer的release。

AlarmManager定时启动Service

private static AlarmManager am;
private static PendingIntent pendingIntent;
/**
 * 使用 AlarmManager 来 定时启动服务
 */
public static void startPendingIntent(Context context) {
    am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(context, MyService.class);//启动示例Service
    pendingIntent = PendingIntent.getService(context, 0, intent, 0);
    long interval = DateUtils.MINUTE_IN_MILLIS * 30;// 30分钟一次
    long firstWake = System.currentTimeMillis() + interval;
    am.setRepeating(AlarmManager.RTC, firstWake, interval, pendingIntent);
}
public static void stopPendingIntent() {
    if (pendingIntent != null) {
        if ( am == null) {
            am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        }
        am.cancel(pendingIntent);
        pendingIntent.cancel();
    }
};

参考:android 使用AlarmManager定时启动service

Timer与AlarmManager的区别

通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,但此时并没有运行,它需要CPU时间片。一旦得到CPU时间片,就会执行run()方法。run()的方法体称为线程体,它包含了要执行的这个线程的内容,run()方法运行结束,此线程也随即终止。

Android 平台上常用的定时器主要有两个:

  • Java的Timer
  • Android的AlarmManager

Timer

Java的Timer类可以用来计划需要循环执行的任务。

简单的说,一个Timer内部封装装了“一个Thread”和“一个TimerTask队列”,这个队列按照一定的方式将任务排队处理。封装的ThreadTimer的构造方法调用时被启动,这个Threadrun方法按照条件去循环这个TimerTask队列,然后调用TimerTaskrun方法。

但是,如果CPU进入了休眠状态,那么这个thread将会因为失去CPU时间片而阻塞,从而造成我们需要的定时任务失效。上述定时任务失效的场景分析:假设定时任务的条件是到了时间xx:yy才能执行,但由于cpu休眠造成线程阻塞的关系,当前系统时间超过了这个时间,即便CPU从终端中恢复了,那么由于条件不满足,定时任务在这一次自然就失效了。

解决方案:它需要用WakeLock让CPU保持唤醒状态。但这样会大量消耗手机电量,大大减短手机待机时间。这种方式不能满足我们的需求。

注:TimerTask实现Runnable接口,但它的run方法只是简单的在Timer中封装的Thread中被调用,并未将其放在其它线程中执行。也就是说timer单线程执行的,那么问题来了,为何要这么费劲的封装一个Runnable接口又不进行多线程调用?

AlarmManager

AlarmManager是Android 系统封装的用于管理RTC的模块,RTC(Real Time Clock)是一个独立的硬件时钟,可以在 CPU休眠时正常运行,在预设的时间到达时,通过中断唤醒CPU。这意味着,如果我们用 AlarmManager来定时执行任务,CPU 可以正常的休眠,只有在需要运行任务时醒来一段很短的时间。

参考:Timer与AlarmManager的区别

Switch开关

Switch mSwitch = (Switch)view.findViewById(R.id.setting_switch_notification_word); 
    mSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener(){

        public void onCheckedChanged(CompoundButton buttonView,boolean isChecked) {
            if (isChecked) {    
                // switch on,开启通知栏单词
                intentNotiService = new Intent(mContext, ServiceNotification.class);
                pendingIntentNotiService = PendingIntent.getService(mContext, 0, intentNotiService, 0);
                minute = 1;
                startPendingIntent(pendingIntentNotiService);
            } else {
                //switch off,关闭通知栏单词
                stopPendingIntent(pendingIntentNotiService);
            }
        }
    });

2015-8-21

解决定时启动通知服务时pop up无效

在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,失去焦点时若未输入任何内容,则回到默认值

关于离开EditText时无法失焦(触摸EditText外的位置)

在其parent view设置xml属性:

android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"

隐藏软键盘keyboard

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

改进:通过广播Broadcast启动服务service

Broadcast静态registerReceiver与动态注册的区别

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

Slidingmenu的bug

在设置如下时

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);  

让悬浮窗显示在状态栏上层

LayoutParams.TYPE_SYSTEM_ERROR //在全屏区域显示(可在状态栏显示,覆盖状态栏)
LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_LAYOUT_IN_SCREEN;

注:

  • 这样设置下拉通知栏也会显示在悬浮窗下层。
  • FLAG_NOT_TOUCH_MODALFLAG_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

  • addView:添加一个悬浮窗,
  • updateViewLayout:更新悬浮窗的参数,
  • removeView:移除悬浮窗。

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);
}

参考:

  1. Android悬浮窗实现 使用WindowManager
  2. getGlobalVisibleRect和getLocalVisibleRect

获取状态栏高度

public static int getStatusBarHeight(Context context) {  
    if (statusBarHeight == 0) {  
        try {  
            Class c = Class.forName("com.android.internal.R$dimen");  
            Object o = c.newInstance();  
            Field field = c.getField("status_bar_height");  
            int x = (Integer) field.get(o);  
            statusBarHeight = context.getResources().getDimensionPixelSize(x);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
    return statusBarHeight;  
}

参考:Android桌面悬浮窗效果实现,仿360手机卫士悬浮窗效果

TextView横向自动滚动实现跑马灯效果

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时才会显示走马灯效果

判断当前界面是否是桌面

private boolean isHome() {  
    ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);  
    List rti = mActivityManager.getRunningTasks(1);  
    return getHomes().contains(rti.get(0).topActivity.getPackageName());  
} 

获得属于桌面的应用的应用包名称

private List<String> getHomes() {  
    List<String> names = new ArrayList<String>();  
    PackageManager packageManager = this.getPackageManager();  
    Intent intent = new Intent(Intent.ACTION_MAIN);  
    intent.addCategory(Intent.CATEGORY_HOME);  
    List resolveInfo = packageManager.queryIntentActivities(intent,  
            PackageManager.MATCH_DEFAULT_ONLY);  
    for (ResolveInfo ri : resolveInfo) {  
        names.add(ri.activityInfo.packageName);  
    }  
    return names;  //返回包含所有包名的字符串列表
}  

2015-9-3

PendingIntent

flag

  • 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

SeekBar拖动条

SeekBar总是自动获取焦点(点击/触摸SeekBar外部区域时,thumb会有响应(变色))

.xml中给添加属性:

android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"

TextView显示音标

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设置不同的字体风格

悬浮窗里只用了一个TextView,便于显示跑马灯效果。为了显示音标使用了TOPhonetic字体,但这样显示的英文和中文都不太喜欢,想只有音标使用TOPhonetic字体,单词和释义跟随手机当前字体。

1.使用Html.fromHtml

如:

mTextView.setTextView(Html.fromHtml("<font color='red'><b>" + "红色字体"
            + "b>font>TextView学习显示不同颜色"));

Textview并不支持所有的html标签。如果更复杂的,可以直接使用webview组件。
查找资料有人说This is some text!,想要更改字体必须手机上安装了此字体。(未测试是否可行,但直接使用的确无效)

String Resources支持的tag

-  (supports attributes "href")
- 
- 
- 
-  (supports attributes "height", "size", "fgcolor" and "bicolor", as integers)
- 
- 
  • - - - - - - -

    Html.fromHtml()支持的tag

    -  (supports attribute "href")
    - 
    - 
    - 
    -
    - - -
    - - (supports attributes "color" and "face") - - (supports attribute "src". Note: you have to include an ImageGetter to handle retrieving a Drawable for this tag) -

    - - - - - -

    支持的颜色:

    - aqua
    - black
    - blue
    - fuchsia
    - green
    - grey
    - lime
    - maroon
    - navy
    - olive
    - purple
    - red
    - silver
    - teal
    - white
    - yellow
    

    参考:
    1.HTML in TextViews
    2.Android 字体设置注意的地方

    2.使用Spannable

    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_INCLUSIVESPAN_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
    换了数据后,一切正常显示。。也不用替换字体了~

    SQLite数据库

    记得之前有做笔记的,但是竟然找不到了。。
    以前在实习公司用过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 ?

    Button的点击和父控件的冲突

    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

    RecyclerView添加PopMenu菜单

    在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

    SQLite数据库

    获取所有表名

    db = wordsDbHelper.getReadableDatabase();
    List<String> tableList = new ArrayList<String>();
    Cursor cursor = db.rawQuery("SELECT name FROM sqlite_master WHERE type='table' AND name!='android_metadata' order by name", null);
    while(cursor.moveToNext()){
        tableList.add(cursor.getString(0));
    }
    

    参考:How to select all tables names instead android_metadata android SQLite

    表重命名

    public static void alterTableName(String oldName, String newName) {
        if ( ! getTableList().contains(newName) ) {
            db = wordsDbHelper.getReadableDatabase();
            db.execSQL("ALTER TABLE " + oldName + " RENAME TO " + newName + ";");
            db.close();
        }
    }
    

    RecyclerView详细使用

    参考:RecyclerView使用介绍

    RecyclerView Adapter更新数据集

    public void updateList(List newList) {
        this.wordsList = newList;
        notifyDataSetChanged();
    }
    

    AlertDialog对话框

    参考:Android的AlertDialog详解

    2015-9-10

    RecyclerView添加点击事件,并获取item position

    参考:
    1.RecyclerView使用介绍
    2.Easy Implementation of RecyclerView custom onItemClickListener

    RecyclerView水平滑动类似viewpager——继承SnappingRecyclerView

    参考:
    1.SnappingRecyclerView.java
    2.Snappy scrolling in RecyclerView

    RecyclerView.Adapter根据viewType动态加载不同的item布局

    在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

    保存和恢复RecyclerView(Scroll)的精确滑动位置

    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 ListView
    • 2.Retain & restore recycler view scroll position

    2015-9-15
    3.#保存和恢复RecyclerView(Scroll)的精确滑动位置——改进版
    尝试了好几天,终于想到了一种比较好的方式,能保存精确位置至本地,随意切换列表数据也能恢复相应表的浏览位置,不过还是有一点点缺陷,第一页的item会跳动一下。

    • scrollToPosition是根据你的操作方向来判断目标item是显示在顶端还是显示在底端的,手指上滑则显示在底端,手指下滑则显示在顶端。那么在切换列表时,则是根据现在的currentPositionscrollToPosition(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);
                  }
              }
          }
      }
      

    slidingmenu切换fragment时的优化

    避免每次切换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

    联网解析JSON数据

    本来想用金山的,但一直没收到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

    RecyclerView水平滑动类似viewpager——自定义方法实现

    之前继承的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;
     }
    }
    

    actionbar下拉列表——PopupWindow

    View view = LayoutInflater.from(context).inflate(R.layout.popmenu, null);       
    popupWindow = new PopupWindow(view, 100, LayoutParams.WRAP_CONTENT);
    // 使其聚集
    popupWindow.setFocusable(true);
    // 设置允许在外点击消失
    popupWindow.setOutsideTouchable(true);
    //刷新状态(必须刷新否则无效)
    popupWindow.update();
    // 这个是为了点击“返回Back”也能使其消失,并且并不会影响你的背景
    popupWindow.setBackgroundDrawable(new BitmapDrawable()); 
    
    popupWindow.showAsDropDown(v); //设置显示位置
    

    参考:

    • 1.android 下拉菜单实现详解 PopupWindow

    2015-9-18

    RecyclerView高度wrap_content无效

    自定义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: [email protected]
     * 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.
     * 

    * 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高度

    在ListView下面有一个Button,ListView设置高度为wrap_content,当ListView高度超出屏幕时,Button就不显示了。
    解决:
    给ListView增加

    android:layout_weight="1" 
    

    2015-9-21

    SearchView

    按返回键关闭SearchView

    @Override
    public void onBackPressed() {
        if ( searchView != null ) {
            if (!searchView.isIconified()) {
                searchView.setIconified(true);
            } else {
                super.onBackPressed();
            }
        }
    }
    

    参考:How do I close a SearchView programmatically?

    点击外部关闭SearchView

    public void setupUI(View view) {
    
        if(!(view instanceof SearchView)) {
    
            view.setOnTouchListener(new OnTouchListener() {
    
                public boolean onTouch(View v, MotionEvent event) {
                    searchMenuItem.collapseActionView();
                    return false;
                }
    
            });
        }
    
        //If a layout container, iterate over children and seed recursion.
        if (view instanceof ViewGroup) {
    
            for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
    
                View innerView = ((ViewGroup) view).getChildAt(i);
    
                setupUI(innerView);
            }
        }
    }
    

    参考:how to make searchview loose focus and collapse when clicked elsewhere on activity

    2015-9-22

    Sqlite判断记录是否存在

    Cursor cursor = db.query(……);
    if (cursor.moveToNext())
        return true;
    else
        return false;
    

    Sqlite模糊查询

    String[] columns = { COLUMN_WORD, COLUMN_DEFINITION };
    String selection = COLUMN_DEFINITION + " like ? "; 
    String[] selectionArgs = new String[]{ "%" + value + "%" };
    
    db = wordsDbHelper.getReadableDatabase();
    Cursor cursor = db.query(tableName, columns, selection, selectionArgs, null, null, COLUMN_WORD);
    

    Bundle实现Android Activity间消息的传递

    Intent intent = new Intent();  
    intent.setClass(TestBundle.this, Target.class);  
    Bundle mBundle = new Bundle();  
    mBundle.putString("Data", "ray'blog");//压入数据  
    intent.putExtras(mBundle);  
    startActivity(intent);  
    
    Bundle bundle = getIntent().getExtras();    
    String data=bundle.getString("Data");//取出数据
    

    Volley

    JsonRequest

    RequestQueue mQueue = Volley.newRequestQueue(context);  
    JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://m.weather.com.cn/data/101010100.html", null,  
        new Response.Listener() {  
            @Override  
            public void onResponse(JSONObject response) {  
                Log.d("TAG", response.toString());  
            }  
        }, new Response.ErrorListener() {  
            @Override  
            public void onErrorResponse(VolleyError error) {  
                Log.e("TAG", error.getMessage(), error);  
            }  
        }); 
    mQueue.add(jsonObjectRequest);  
    

    2015-9-24

    半透明背景

    res/values/color.xml

    <color name="transparent_background">#50000000color>
    

    #5000000前两位是透明的效果参数从00—99(透明—不怎么透明),后6位是颜色的设置

    参考:android 成长 UI 学习之 Activity 透明,半透明效果的设置transparent

    圆角边框

    drawable目录里定义一个corners_bg.xml:

        
    <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
    • 适合播放较大文件,文件应该存储在SD卡上,而不是在资源文件里
    • 资源占用量较高,延迟时间较长。
    • 不支持多个音频同时播放

    (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资源占用量低和反应延迟小;
    • 支持自行设置的品质、音量、播放比率等参数;
    • 异步线程,占用资源少,可以同时合成多种音效;

    修改ActionBar icon大小

    • API < 17,res/values/styles.xml :

      <item name="android:actionButtonStyle">@style/ActionButtonStyleitem>
      
      
      
    • API > 17,res/values-v17/styles.xml :

      <item name="android:actionButtonStyle">@style/ActionButtonStyleitem>
      
      
      

      参考:Is there a way to reduce the spacing between the Action Item Icons on Action Bar?

    修改actionbar的高度

    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
        style>
    resources>
    

    manifest.xml:

    label="@string/app_name"
        android:theme="@style/AppTheme"
        android:icon="@drawable/ic_launcher"
     >
    

    参考:ActonBar介绍-修改actionbar的高度

    修改SearchView样式

    文字颜色

    输入的文字:

    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("" + getResources().getString(R.string.search_input) + ""));
    

    参考:关于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);
    

    修改Cursor颜色

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.entity_list_actions, menu);
        final SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView();
        final int textViewID = searchView.getContext().getResources().getIdentifier("android:id/search_src_text",null, null);
        final AutoCompleteTextView searchTextView = (AutoCompleteTextView) searchView.findViewById(textViewID);
        try {
            Field mCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes");
            mCursorDrawableRes.setAccessible(true);
            mCursorDrawableRes.set(searchTextView, 0); //This sets the cursor resource ID to 0 or @null which will make it visible on white background
        } catch (Exception e) {}
        return super.onCreateOptionsMenu(menu);
    }
    

    参考:Changing the cursor color in SearchView without ActionBarSherlock

    ActionBar返回上一个Activity

    在此Activity里设置

    ActionBar mActionBar = getActionBar();
    mActionBar.setDisplayHomeAsUpEnabled(true);//show back button
    

    在Manifest.xml文件中设置这个Activity的parentActivity

    parentActivityName=".MainActivity"
        android:name=".BackActionBarActivity"
        android:launchMode="singleTask"
        android:label="@string/title_activity_back_action_bar">
        name="android.support.PARENT_ACTIVITY"
            android:value=".MainActivity"/>
    
    

    参考:Android ActionBar 返回上一个Activity

    修改指定Activity的Actionbar

    styles.xml:

    
    

    Manifest.xml:

    <activity
            android:name="com.example.CustomActivity"
            android:theme="@style/CustomActivityTheme" >
    activity>
    

    参考:Change the actionbar homeAsUpIndicator Programamtically

    2015-9-25

    RecyclerView添加分割线

    使用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

    Volley加载网络图片

    ImageRequest的用法

    1. 创建一个RequestQueue对象。
    2. 创建一个Request对象。
    3. 将Request对象添加到RequestQueue里面。

      RequestQueue mQueue = Volley.newRequestQueue(context);
      ImageRequest imageRequest = new ImageRequest(
          "http://developer.android.com/images/home/aw_dac.png",
          new Response.Listener() {
              @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

    ImageLoader也可以用于加载网络上的图片,并且它的内部也是使用ImageRequest来实现的,不过ImageLoader明显要比ImageRequest更加高效,因为它不仅可以帮我们对图片进行缓存,还可以过滤掉重复的链接,避免重复发送请求。

    1. 创建一个RequestQueue对象。
    2. 创建一个ImageLoader对象。
    3. 获取一个ImageListener对象。
    4. 调用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加载网络图片

    Textview文字末尾拼接带本地图片背景文字

    textView = (TextView) findViewById(R.id.text);
    
    ImageGetter imageGetter = new ImageGetter() {
      @Override
      public Drawable getDrawable(String source) {
          int resId = Integer.parseInt(source);
          Drawable drawable = MainActivity.this.getResources()
                  .getDrawable(resId);
          drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
                  drawable.getIntrinsicHeight());
          return drawable;
      }
    };
    
    textView.setText(Html.fromHtml("我要添加一个+R.drawable.ic_launcher+"\">,看到了吗?", imageGetter, null));
    

    文字和背景合并插入正文中

    自定义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

    获取当前日期

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    SimpleDateFormat函数语法:
      
      G 年代标志符
      y 年
      M
      d
      h 时 在上午或下午 (1~12)
      H 时 在一天中 (0~23)
      m
      s 秒
      S 毫秒
      E 星期
      D 一年中的第几天
      F 一月中第几个星期几
      w 一年中第几个星期
      W 一月中第几个星期
      a 上午 / 下午 标记符 
      k 时 在一天中 (1~24)
      K 时 在上午或下午 (0~11)
      z 时区
    
    SimpleDateFormat myFmt=new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
    SimpleDateFormat myFmt1=new SimpleDateFormat("yy/MM/dd HH:mm"); 
    SimpleDateFormat myFmt2=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//等价于now.toLocaleString()
    SimpleDateFormat myFmt3=new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 E ");
    SimpleDateFormat myFmt4=new SimpleDateFormat(
            "一年中的第 D 天 一年中第w个星期 一月中第W个星期 在一天中k时 z时区");
    Date now=new Date();
    System.out.println(myFmt.format(now));
    System.out.println(myFmt1.format(now));
    System.out.println(myFmt2.format(now));
    System.out.println(myFmt3.format(now));
    System.out.println(myFmt4.format(now));
    System.out.println(now.toGMTString());
    System.out.println(now.toLocaleString());
    System.out.println(now.toString());
    

    参考:SimpleDateFormat使用详解

    2015-10-7

    SQLite索引

    索引可大幅减少扫描表所需的时间。 请遵照以下准则:
    索引中的列顺序会影响性能。 WHERE 子句通常使用的列应该放在前面,然后放置 ORDER BY 子句通常使用的列。
    对于包含检索的数据的列,创建覆盖索引。
    避免重复索引。 SQLite 数据库引擎将自动为具有 UNIQUE 或 PRIMARY KEY 限制的列创建索引。

    SQLite复制表数据到已存在的表中

    insert into tagTable select * from sourceTable;  
    //tagTable     目标数据库  
    //sourceTable  源数据库  
    

    参考:Sqlite 将一张表的数据复制到另一张表中

    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 student
    

    5.将teacher这个表重命名为student

    alter table teacher rename to student
    

    结果演示:

    select * from student order by name descdesc降序, asc升序)
    

    这样就可以得到我们想要的结果了。

    参考:Sqlite删除列方法

    2015-10-9

    MediaPlayer从头播放

    ```
    if ( playerSentence != null ) {
        if ( playerSentence.isPlaying() ) {
            playerSentence.seekTo(0);
        } else {
            playerSentence.start();
        }
    }
    ```            
    

    参考:android里用MediaPlayer,当音乐现在正在播放时,点击按钮是如何让音乐从头播放

    判断数据库表是否存在

    Cursor c=db.rawQuery("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='要查询的表名'", null);  
    if (c.getInt(0)==0) {  
        return false;  
    } 
    

    参考:判断sqlite数据库中表是否存在的方法

    2015-10-10

    禁止横屏竖屏切换

    AndroidManifest.xml的需要禁止转向的Activity配置中加入android:screenOrientation="portrait"属性即可(landscape是横向,portrait是纵向)
    参考:Android禁止横屏竖屏切换

    2015-10-11

    文件下载

    OkHttp

    参考:

    • Android OkHttp完全解析 是时候来了解OkHttp了
    • okhttp-utils

    从路径获取文件名

    //      方法一:  
    
        File tempFile =new File( fName.trim());  
        String fileName = tempFile.getName();            
        System.out.println("fileName = " + fileName);  
    
    //      方法二:    
        String fName = fName.trim();    
        String fileName = fName.substring(fName.lastIndexOf(File.separator)+1);            
        System.out.println("fileName = " + fileName);  
    
    //      方法三:    
        String fName = fName.trim();    
        String temp[] = fName.split("\\\\"); /**split里面必须是正则表达式,"\\"的作用是对字符串转义*/    
        String fileName = temp[temp.length-1];  
    

    参考:3种Java从文件路径中获取文件名的方法

    2016-02-28

    CardView适配

    • 不同 SDK 版本(低于 Lollipop 21)上的边距(Margin)效果

    在低版本中设置了 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 开发过程中要注意的细节

    你可能感兴趣的:(Android,Android)