1 2 3 4 5 6 7 8 9 10 11 12 |
title: { text: '', style: { display: 'none' } }, subtitle: { text: '', style: { display: 'none' } }, |
1 2 3 4 5 6 7 8 9 10 11 12 |
title: { text: '', style: { display: 'none' } }, subtitle: { text: '', style: { display: 'none' } }, |
效果图如下:
关键字搜索不区分大小写,TextView换成EditText也同样适用。
Activity部局代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <EditText android:id="@+id/edtKeyword" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginLeft="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:layout_marginRight="8dp" android:ems="10" android:inputType="textPersonName" app:layout_constraintEnd_toStartOf="@+id/btnSearch" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/btnSearch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:layout_marginRight="8dp" android:text="搜索" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/tvContent" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="8dp" android:layout_marginLeft="8dp" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:layout_marginRight="8dp" android:layout_marginBottom="8dp" android:ems="10" android:gravity="start|top" android:inputType="textMultiLine" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/edtKeyword" /> </androidx.constraintlayout.widget.ConstraintLayout> |
Activity代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
package com.example.highlightstrapp; import android.app.Activity; import android.graphics.Color; import android.os.Bundle; import android.text.SpannableString; import android.text.Spanned; import android.text.style.BackgroundColorSpan; import android.text.style.ForegroundColorSpan; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; public class MainActivity extends Activity { private EditText edtKeyword; private TextView tvContent; private Button btnSearch; private String mContent = " There are moments in life when you miss someone so much that you " + "just want to pick them from your dreams and hug them for real! Dream what you want to " + "dream;go where you want to go;be what you want to be,because you have only one life " + "and one chance to do all the things you want to do.\n" + "\n" + " May you have enough happiness to make you sweet,enough trials to make you strong," + "enough sorrow to keep you human,enough hope to make you happy? Always put yourself " + "in others’shoes.If you feel that it hurts you,it probably hurts the other person, too.\n" + "\n" + " The happiest of people don’t necessarily have the best of everything;they just " + "make the most of everything that comes along their way.Happiness lies for those who " + "cry,those who hurt, those who have searched,and those who have tried,for only they " + "can appreciate the importance of people"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); initEvents(); } private void initEvents() { btnSearch.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { hightStr(edtKeyword.getText().toString(), mContent); } }); } private void initViews() { tvContent = findViewById(R.id.tvContent); edtKeyword = findViewById(R.id.edtKeyword); btnSearch = findViewById(R.id.btnSearch); } public void hightStr(String hlStr, String srcString){ tvContent.setText(""); String hlLower = hlStr.toLowerCase(); int lastEnd = 0; SpannableString spannableString = new SpannableString(srcString); for(int i = 0; i <= srcString.length() - hlStr.length(); i++){ if(srcString.substring(i, i + hlStr.length()).toLowerCase().equals(hlLower)){ spannableString.setSpan(new ForegroundColorSpan(Color.RED), i, i + hlStr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); spannableString.setSpan(new BackgroundColorSpan(Color.YELLOW), i, i + hlStr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } tvContent.append(spannableString); } } |
本文基于Android8.1系统,应用需要为系统应用
实现方法参考此链接:https://blog.csdn.net/weijuantang/article/details/41379027
通过反射实现设置语言功能的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
private void updateLanguage(Locale locale) { Log.d("ANDROID_LAB", locale.toString()); try { Object objIAm, objActMagNative; Class ClzAm = ActivityManager.class; ActivityManager am = (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE); Class clzIAM = Class.forName("android.app.IActivityManager"); Class clzAmNative = Class.forName("android.app.ActivityManagerNative"); Method mtdActMagNative$getDefault = clzAmNative.getDeclaredMethod("getDefault"); // IActivityManager iActMag = ActivityManagerNative.getDefault(); Method getIamMethod = ClzAm.getMethod("getService"); objIAm = getIamMethod.invoke(am); ////objIAm = mtdActMagNative$getDefault.invoke(clzAmNative); // Configuration config = iActMag.getConfiguration(); Method mtdIActMag$getConfiguration = clzIAM.getDeclaredMethod("getConfiguration"); Configuration config = (Configuration) mtdIActMag$getConfiguration.invoke(objIAm); config.locale = locale; config.setLocales(new LocaleList(locale)); // iActMag.updateConfiguration(config); // 此处需要声明权限:android.permission.CHANGE_CONFIGURATION // 会重新调用 onCreate(); Method updatePersistentConfigurationMethod = clzIAM.getMethod("updatePersistentConfiguration", Configuration.class); updatePersistentConfigurationMethod.invoke(objIAm, config); Class[] clzParams = { Configuration.class }; Method mtdIActMag$updateConfiguration = clzIAM.getDeclaredMethod( "updateConfiguration", clzParams); mtdIActMag$updateConfiguration.invoke(objIAm, config); } catch (Exception e) { e.printStackTrace(); } } |
调用方法:
1 2 |
updateLanguage(new Locale("zh", "CN")); updateLanguage(new Locale("en", "US")); |
本文适用于Android8.0及以上系统,应用必须为系统应用
获取时区列表(>=Android8.0)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
import android.content.Context; import android.content.res.XmlResourceParser; import android.icu.text.TimeZoneFormat; import android.icu.text.TimeZoneNames; import android.os.Build; import android.text.BidiFormatter; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.TextDirectionHeuristics; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.style.TtsSpan; import android.util.Log; import android.view.View; import androidx.annotation.RequiresApi; import androidx.core.text.TextDirectionHeuristicsCompat; import com.zht.car.zhtsettings.R; import org.xmlpull.v1.XmlPullParserException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TimeZone; @RequiresApi(api = Build.VERSION_CODES.O) public class ZoneGetter { private static final String XMLTAG_TIMEZONE = "timezone"; private static final String TAG = "ZoneGetter"; public static final String KEY_ID = "id"; // value: String /** * @deprecated Use {@link #KEY_DISPLAY_LABEL} instead. */ @Deprecated public static final String KEY_DISPLAYNAME = "name"; // value: String public static final String KEY_DISPLAY_LABEL = "display_label"; // value: CharSequence /** * @deprecated Use {@link #KEY_OFFSET_LABEL} instead. */ @Deprecated public static final String KEY_GMT = "gmt"; // value: String public static final String KEY_OFFSET = "offset"; // value: int (Integer) public static final String KEY_OFFSET_LABEL = "offset_label"; // value: CharSequence public static List<Map<String, Object>> getZonesList(Context context) { final Locale locale = context.getResources().getConfiguration().locale; final Date now = new Date(); final TimeZoneNames timeZoneNames = TimeZoneNames.getInstance(locale); final ZoneGetterData data = new ZoneGetterData(context); // Work out whether the display names we would show by default would be ambiguous. final boolean useExemplarLocationForLocalNames = shouldUseExemplarLocationForLocalNames(data, timeZoneNames); // Generate the list of zone entries to return. List<Map<String, Object>> zones = new ArrayList<Map<String, Object>>(); for (int i = 0; i < data.zoneCount; i++) { TimeZone tz = data.timeZones[i]; CharSequence gmtOffsetText = data.gmtOffsetTexts[i]; CharSequence displayName = getTimeZoneDisplayName(data, timeZoneNames, useExemplarLocationForLocalNames, tz, data.olsonIdsToDisplay[i]); if (TextUtils.isEmpty(displayName)) { displayName = gmtOffsetText; } int offsetMillis = tz.getOffset(now.getTime()); Map<String, Object> displayEntry = createDisplayEntry(tz, gmtOffsetText, displayName, offsetMillis); zones.add(displayEntry); } return zones; } private static boolean shouldUseExemplarLocationForLocalNames(ZoneGetterData data, TimeZoneNames timeZoneNames) { final Set<CharSequence> localZoneNames = new HashSet<>(); final Date now = new Date(); for (int i = 0; i < data.zoneCount; i++) { final String olsonId = data.olsonIdsToDisplay[i]; if (data.localZoneIds.contains(olsonId)) { final TimeZone tz = data.timeZones[i]; CharSequence displayName = getZoneLongName(timeZoneNames, tz, now); if (displayName == null) { displayName = data.gmtOffsetTexts[i]; } final boolean nameIsUnique = localZoneNames.add(displayName); if (!nameIsUnique) { return true; } } } return false; } private static Map<String, Object> createDisplayEntry( TimeZone tz, CharSequence gmtOffsetText, CharSequence displayName, int offsetMillis) { Map<String, Object> map = new HashMap<>(); map.put(KEY_ID, tz.getID()); map.put(KEY_DISPLAYNAME, displayName.toString()); map.put(KEY_DISPLAY_LABEL, displayName); map.put(KEY_GMT, gmtOffsetText.toString()); map.put(KEY_OFFSET_LABEL, gmtOffsetText); map.put(KEY_OFFSET, offsetMillis); return map; } private static CharSequence getTimeZoneDisplayName(ZoneGetterData data, TimeZoneNames timeZoneNames, boolean useExemplarLocationForLocalNames, TimeZone tz, String olsonId) { final Date now = new Date(); final boolean isLocalZoneId = data.localZoneIds.contains(olsonId); final boolean preferLongName = isLocalZoneId && !useExemplarLocationForLocalNames; String displayName; if (preferLongName) { displayName = getZoneLongName(timeZoneNames, tz, now); } else { // Canonicalize the zone ID for ICU. It will only return valid strings for zone IDs // that match ICUs zone IDs (which are similar but not guaranteed the same as those // in timezones.xml). timezones.xml and related files uses the IANA IDs. ICU IDs are // stable and IANA IDs have changed over time so they have drifted. // See http://bugs.icu-project.org/trac/ticket/13070 / http://b/36469833. String canonicalZoneId = android.icu.util.TimeZone.getCanonicalID(tz.getID()); if (canonicalZoneId == null) { canonicalZoneId = tz.getID(); } displayName = timeZoneNames.getExemplarLocationName(canonicalZoneId); if (displayName == null || displayName.isEmpty()) { // getZoneExemplarLocation can return null. Fall back to the long name. displayName = getZoneLongName(timeZoneNames, tz, now); } } return displayName; } /** * Returns the long name for the timezone for the given locale at the time specified. * Can return {@code null}. */ private static String getZoneLongName(TimeZoneNames names, TimeZone tz, Date now) { final TimeZoneNames.NameType nameType = tz.inDaylightTime(now) ? TimeZoneNames.NameType.LONG_DAYLIGHT : TimeZoneNames.NameType.LONG_STANDARD; return names.getDisplayName(tz.getID(), nameType, now.getTime()); } private static List<String> readTimezonesToDisplay(Context context) { List<String> olsonIds = new ArrayList<String>(); try (XmlResourceParser xrp = context.getResources().getXml(R.xml.timezones)) { while (xrp.next() != XmlResourceParser.START_TAG) { continue; } xrp.next(); while (xrp.getEventType() != XmlResourceParser.END_TAG) { while (xrp.getEventType() != XmlResourceParser.START_TAG) { if (xrp.getEventType() == XmlResourceParser.END_DOCUMENT) { return olsonIds; } xrp.next(); } if (xrp.getName().equals(XMLTAG_TIMEZONE)) { String olsonId = xrp.getAttributeValue(0); olsonIds.add(olsonId); } while (xrp.getEventType() != XmlResourceParser.END_TAG) { xrp.next(); } xrp.next(); } } catch (XmlPullParserException xppe) { Log.e(TAG, "Ill-formatted timezones.xml file"); } catch (java.io.IOException ioe) { Log.e(TAG, "Unable to read timezones.xml file"); } return olsonIds; } /** * Get the GMT offset text label for the given time zone, in the format "GMT-08:00". This will * also add TTS spans to give hints to the text-to-speech engine for the type of data it is. * * @param tzFormatter The timezone formatter to use. * @param locale The locale which the string is displayed in. This should be the same as the * locale of the time zone formatter. * @param tz Time zone to get the GMT offset from. * @param now The current time, used to tell whether daylight savings is active. * @return A CharSequence suitable for display as the offset label of {@code tz}. */ private static CharSequence getGmtOffsetText(TimeZoneFormat tzFormatter, Locale locale, TimeZone tz, Date now) { final SpannableStringBuilder builder = new SpannableStringBuilder(); final String gmtPattern = tzFormatter.getGMTPattern(); final int placeholderIndex = gmtPattern.indexOf("{0}"); final String gmtPatternPrefix, gmtPatternSuffix; if (placeholderIndex == -1) { // Bad pattern. Replace with defaults. gmtPatternPrefix = "GMT"; gmtPatternSuffix = ""; } else { gmtPatternPrefix = gmtPattern.substring(0, placeholderIndex); gmtPatternSuffix = gmtPattern.substring(placeholderIndex + 3); // After the "{0}". } if (!gmtPatternPrefix.isEmpty()) { appendWithTtsSpan(builder, gmtPatternPrefix, new TtsSpan.TextBuilder(gmtPatternPrefix).build()); } int offsetMillis = tz.getOffset(now.getTime()); final boolean negative = offsetMillis < 0; final TimeZoneFormat.GMTOffsetPatternType patternType; if (negative) { offsetMillis = -offsetMillis; patternType = TimeZoneFormat.GMTOffsetPatternType.NEGATIVE_HM; } else { patternType = TimeZoneFormat.GMTOffsetPatternType.POSITIVE_HM; } final String gmtOffsetPattern = tzFormatter.getGMTOffsetPattern(patternType); final String localizedDigits = tzFormatter.getGMTOffsetDigits(); final int offsetHours = (int) (offsetMillis / DateUtils.HOUR_IN_MILLIS); final int offsetMinutes = (int) (offsetMillis / DateUtils.MINUTE_IN_MILLIS); final int offsetMinutesRemaining = Math.abs(offsetMinutes) % 60; for (int i = 0; i < gmtOffsetPattern.length(); i++) { char c = gmtOffsetPattern.charAt(i); if (c == '+' || c == '-' || c == '\u2212' /* MINUS SIGN */) { final String sign = String.valueOf(c); appendWithTtsSpan(builder, sign, new TtsSpan.VerbatimBuilder(sign).build()); } else if (c == 'H' || c == 'm') { final int numDigits; if (i + 1 < gmtOffsetPattern.length() && gmtOffsetPattern.charAt(i + 1) == c) { numDigits = 2; i++; // Skip the next formatting character. } else { numDigits = 1; } final int number; final String unit; if (c == 'H') { number = offsetHours; unit = "hour"; } else { // c == 'm' number = offsetMinutesRemaining; unit = "minute"; } appendWithTtsSpan(builder, formatDigits(number, numDigits, localizedDigits), new TtsSpan.MeasureBuilder().setNumber(number).setUnit(unit).build()); } else { builder.append(c); } } if (!gmtPatternSuffix.isEmpty()) { appendWithTtsSpan(builder, gmtPatternSuffix, new TtsSpan.TextBuilder(gmtPatternSuffix).build()); } CharSequence gmtText = new SpannableString(builder); // Ensure that the "GMT+" stays with the "00:00" even if the digits are RTL. final BidiFormatter bidiFormatter = BidiFormatter.getInstance(); boolean isRtl = TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL; gmtText = bidiFormatter.unicodeWrap(gmtText, isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR); return gmtText; } private static void appendWithTtsSpan(SpannableStringBuilder builder, CharSequence content, TtsSpan span) { int start = builder.length(); builder.append(content); builder.setSpan(span, start, builder.length(), 0); } // Input must be positive. minDigits must be 1 or 2. private static String formatDigits(int input, int minDigits, String localizedDigits) { final int tens = input / 10; final int units = input % 10; StringBuilder builder = new StringBuilder(minDigits); if (input >= 10 || minDigits == 2) { builder.append(localizedDigits.charAt(tens)); } builder.append(localizedDigits.charAt(units)); return builder.toString(); } private static final class ZoneGetterData { public final String[] olsonIdsToDisplay; public final CharSequence[] gmtOffsetTexts; public final TimeZone[] timeZones; public final Set<String> localZoneIds; public final int zoneCount; public ZoneGetterData(Context context) { final Locale locale = context.getResources().getConfiguration().locale; final TimeZoneFormat tzFormatter = TimeZoneFormat.getInstance(locale); final Date now = new Date(); final List<String> olsonIdsToDisplayList = readTimezonesToDisplay(context); // Load all the data needed to display time zones zoneCount = olsonIdsToDisplayList.size(); olsonIdsToDisplay = new String[zoneCount]; timeZones = new TimeZone[zoneCount]; gmtOffsetTexts = new CharSequence[zoneCount]; for (int i = 0; i < zoneCount; i++) { final String olsonId = olsonIdsToDisplayList.get(i); olsonIdsToDisplay[i] = olsonId; final TimeZone tz = TimeZone.getTimeZone(olsonId); timeZones[i] = tz; gmtOffsetTexts[i] = getGmtOffsetText(tzFormatter, locale, tz, now); } // Create a lookup of local zone IDs. localZoneIds = new HashSet<String>(); //TODO: // for (String olsonId : libcore.icu.TimeZoneNames.forLocale(locale)) { // localZoneIds.add(olsonId); // } } } } |
设置时区:
1 2 3 |
AlarmManager am = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); am.setTimeZone(mTimeZoneList.get(position).get(ZoneGetter.KEY_ID).toString()); getContext().sendBroadcast(new Intent(Intent.ACTION_TIME_CHANGED)); |
获取时区:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public static String getTimeZoneText(TimeZone tz, boolean includeName) { Date now = new Date(); SimpleDateFormat gmtFormatter = new SimpleDateFormat("ZZZZ"); gmtFormatter.setTimeZone(tz); String gmtString = gmtFormatter.format(now); BidiFormatter bidiFormatter = BidiFormatter.getInstance(); Locale l = Locale.getDefault(); boolean isRtl = TextUtils.getLayoutDirectionFromLocale(l) == View.LAYOUT_DIRECTION_RTL; gmtString = bidiFormatter.unicodeWrap(gmtString, isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR); if (!includeName) { return gmtString; } return gmtString; } |
基于Android8.1系统测试
需要是系统应用
需要在Manifest.xml中声明如下权限:
1 2 |
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> |
重新定义一下系统常量(在系统里是hide的)
1 2 3 4 |
public static final String EXTRA_TIME_PREF_24_HOUR_FORMAT = "android.intent.extra.TIME_PREF_24_HOUR_FORMAT"; public static final int EXTRA_TIME_PREF_VALUE_USE_12_HOUR = 0; public static final int EXTRA_TIME_PREF_VALUE_USE_24_HOUR = 1; |
读取当前设置值:
1 |
String hour24 = Settings.System.getString(getContext().getContentResolver(), Settings.System.TIME_12_24); |
设置新的值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
mLayout24h.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mSwitch24h.setChecked(!mSwitch24h.isChecked()); String newValue = mSwitch24h.isChecked() ? "24" : "12"; Log.d(LOG_TAG, "newValue: " + newValue); Settings.System.putString(getContext().getContentResolver(), Settings.System.TIME_12_24, newValue); Intent timeChanged = new Intent(Intent.ACTION_TIME_CHANGED); int timeFormatPreference = mSwitch24h.isChecked() ? EXTRA_TIME_PREF_VALUE_USE_24_HOUR : EXTRA_TIME_PREF_VALUE_USE_12_HOUR; timeChanged.putExtra(EXTRA_TIME_PREF_24_HOUR_FORMAT, timeFormatPreference); getContext().sendBroadcast(timeChanged); } }); |
今天在执行如下操作时发现一个奇怪的问题,首先使用update更新数据库,然后select数据进行上传,结果select出来的数据总是旧数据。查了一天更新和上传那块的代码也没发现什么问题,后来发现是greenDao的问题。
问题:greenDao中创建DaoSession对象时默认是使用缓存的,这样select出来的数据可能就不是最新的
解决方法:
方法一:创建DaoSession对象的时候传入IdentityScopeType.None参数默认禁止缓存,示例如下:
1 2 |
mDaoMaster = new DaoMaster(db); mDaoSession = mDaoMaster.newSession(IdentityScopeType.None); |
方法二:在select的时候执行一下DaoSession对象的clear方法(注:本方法笔者未验证是否有其他问题,请谨慎使用),如下:
1 |
getDaoSession().clear();; |
本文基于Android8.1系统编写
今天想实现自定义的back和home键,比着葫芦画瓢,把back和home键的部局抄过来了。但怎么也没找到back和home键的java功能实现部分。
后来度娘了一下才找到实现的原理。
参考链接:http://blog.chinaunix.net/uid-701715-id-3054850.html
自己也记录一下
在部局中可以看到,back和home键用了一个自定义view,开始我还以为是为了实现按钮的动画效果才自定义了一个这个view,现在发现自己天真了!
KeyButtonView的核心在于,他定义了一个可设置的部局属性keyCode,用于设置按钮对应的键,如下back键的部局代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<com.android.systemui.statusbar.policy.KeyButtonView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res-auto" android:id="@+id/back" android:layout_width="@dimen/navigation_key_width" android:layout_height="match_parent" android:layout_weight="0" systemui:keyCode="4" android:scaleType="fitCenter" android:contentDescription="@string/accessibility_back" android:paddingTop="15dp" android:paddingBottom="15dp" android:paddingStart="@dimen/navigation_key_padding" android:paddingEnd="@dimen/navigation_key_padding" |
keyCode=”4″对应的为KeyEvent.KEYCODE_BACK键
在KeyButtonView的构造函数中,读取并保存了这个属性:
1 2 3 4 5 6 7 |
public KeyButtonView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs); //...省略前面的代码 mCode = a.getInteger(R.styleable.KeyButtonView_keyCode, 0); //...省略后面的代码 } |
如此,在部局文件中设置的keyCode读到了类中
原理是,拦截onTouch事件,当按钮按下时发送keyCode的ACTION_DOWN操作,当按钮弹开时发送keyCode的ACTION_UP操作,完成一次按键的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
public boolean onTouchEvent(MotionEvent ev) { //...省略部分代码 switch (action) { case MotionEvent.ACTION_DOWN: mDownTime = SystemClock.uptimeMillis(); mLongClicked = false; setPressed(true); if (mCode != 0) { sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime); } else { // Provide the same haptic feedback that the system offers for virtual keys. performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); } playSoundEffect(SoundEffectConstants.CLICK); removeCallbacks(mCheckLongPress); postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout()); break; case MotionEvent.ACTION_MOVE: //...省略 case MotionEvent.ACTION_CANCEL: //...省略 break; case MotionEvent.ACTION_UP: final boolean doIt = isPressed() && !mLongClicked; setPressed(false); // Always send a release ourselves because it doesn't seem to be sent elsewhere // and it feels weird to sometimes get a release haptic and other times not. if ((SystemClock.uptimeMillis() - mDownTime) > 150 && !mLongClicked) { performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE); } if (mCode != 0) { if (doIt) { sendEvent(KeyEvent.ACTION_UP, 0); sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); } else { sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED); } } else { // no key code, just a regular ImageView if (doIt && mOnClickListener != null) { mOnClickListener.onClick(this); sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); } } removeCallbacks(mCheckLongPress); break; } return true; } |
sendEvent函数,先构造KeyEvent对象,然后通过InputManager发送到系统,代码如下:
1 2 3 4 5 6 7 8 9 10 |
void sendEvent(int action, int flags, long when) { //...省略部分代码 final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0; final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, InputDevice.SOURCE_KEYBOARD); InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } |
本文同样适用于高度相关的对应函数
1. getWidth()
View的实际宽度
2. getMeasuredWidth()
是用setMeasureDimension设置的宽度,这个并不能作为最终的View的宽度来使用,只是在onMeasure的时候用户计算的宽度,只能在这个函数里使用?
3. getMeasuredWidthAndState()
和getMeasuredWidth()类似,但是返回的值包括measuredWidth和MEASURED_SIZE_MASK或者MEASURED_STATE_TOO_SMALL的标志位。
4. getMinimumWidth()
应该是在布局设计时,设置的minWidth属性对应的值或者使用函数setMinimumWidth动态设置的值
5. getSuggestedMinimumWidth()
如果View设置了背景,则取mMinWidth和背景的最小宽度中比较大的一个值。
6. 去除padding后的实际宽度
如果需要考虑padding参数,则去除padding后的实际内容宽度为:
getWidth() – getPaddingLeft – getPaddingRight
1. 在onMeasure函数中不能使用getWidth()和getHeight()函数,此时这两个值都是0
2. MeasureSpec,三种度量规格:
本自定义组件实现了一个以高度为标准的正方形TextView组件,如果想以其他标准(最大或者最小宽度、高度)需要再进行修改:
自定义View的代码如下;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import android.content.Context; import android.util.AttributeSet; public class SquareTextView extends android.support.v7.widget.AppCompatTextView { private String LOG_TAG = "SquareTextView"; public SquareTextView(Context context) { super(context); } public SquareTextView(Context context, AttributeSet attrs) { super(context, attrs); } public SquareTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 先让父类计算宽度和高度 super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 以父类计算的高度获取我们想要的宽度 int w = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY); // 如果只使用setMeasuredDimension会造成对齐效果的失效 // setMeasuredDimension(getMeasuredHeight(), getMeasuredHeight()); // 让父类重新计算 super.onMeasure(w, heightMeasureSpec); } } |