<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	
	xmlns:georss="http://www.georss.org/georss"
	xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
	>

<channel>
	<title>Android &#8211; Blog of Code</title>
	<atom:link href="https://www.cztcode.com/tag/android/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.cztcode.com</link>
	<description></description>
	<lastBuildDate>Tue, 23 Jun 2020 12:56:31 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://www.cztcode.com/wp-content/uploads/2024/02/cropped-logo-32x32.webp</url>
	<title>Android &#8211; Blog of Code</title>
	<link>https://www.cztcode.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">217219486</site>	<item>
		<title>Java安卓学习总结（二十一）</title>
		<link>https://www.cztcode.com/2020/2699/</link>
					<comments>https://www.cztcode.com/2020/2699/#respond</comments>
		
		<dc:creator><![CDATA[Jellow]]></dc:creator>
		<pubDate>Tue, 23 Jun 2020 12:56:31 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<guid isPermaLink="false">https://www.cztcode.com/?p=2699</guid>

					<description><![CDATA[Java安卓学习总结（二十一） 第十七章 双版面主从用户界面]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div>


<h2 class="wp-block-heading">第十七章 双版面主从用户界面</h2>



<h3 class="wp-block-heading">有关别名资源</h3>



<p class="is-style-iw-2em">别名资源时一种指向其它资源的特殊资源，存放在res/values中</p>



<pre class="wp-block-preformatted">&lt;<strong>item </strong><strong>name</strong><strong>="activity_masterdetail" </strong><strong>type</strong><strong>="layout"</strong>&gt;@layout/activity_fragment&lt;/<strong>item</strong>&gt;</pre>



<p class="is-style-iw-2em">调用时使用这个别名资源的名字，但是使用的方式完全按照其type指定的方式，使用的实际内容是引用的内容。</p>



<p class="is-style-iw-2em">为平板设备创建的专用可用资源，是与之前的别名资源文件名、目录完全相同的资源，唯一的区别在于有最小的屏幕宽度限制和引用的视图文件不同。</p>



<pre class="wp-block-preformatted">//w600dp
&lt;<strong>item name="activity_masterdetail" type="layout"</strong>&gt;@layout/activity_twopane&lt;/<strong>item</strong>&gt;</pre>



<p class="is-style-iw-2em">android会根据设备屏幕大小自动判断使用的可选资源。</p>



<h3 class="wp-block-heading">有关fragment回调接口</h3>



<p class="is-style-iw-2em">这个问题产生于一个activity下同时托管了两个fragment，由其中一个fragment来启动另一个fragment时需要将后者打入fragmentManager中。由于这个操作的环境是在fragment中，所以需要首先找到托管其的activity，再利用activity的fragmentManager来启动另一个fragment。</p>



<p class="is-style-iw-2em">由于这两个fragment被同一个activity托管，实际上他们都可以由activity直接管理，如果要经由另一个fragment才能启动的话，限制了fragment的独立性。</p>



<p class="is-style-iw-2em">此时应当使用回调接口，其做法是将activity启动一个fragment的功能（或者其它需要利用activity实现的）在一个接口中实现，那么在fragment中就可以实例化出该接口，使其赋值为托管的activity，就可以用这个回调接口的实例去完成fragment的处理任务。</p>



<pre class="wp-block-preformatted"><strong>private </strong>Callbacks <strong>mCallbacks</strong>;

<strong>public interface </strong>Callbacks
{
    <strong>void </strong>onCrimeSelected(Crime crime);
    <strong>void </strong>onCrimeDeleteDirectly(Crime crime);
}

@Override
<strong>public void </strong>onAttach(Activity activity) {
    <strong>super</strong>.onAttach(activity);
    <strong>mCallbacks</strong>=(Callbacks)activity;
}
@Override
<strong>public void </strong>onDetach() {
<strong>super</strong>.onDetach();
<strong>mCallbacks</strong>=<strong>null</strong>;
}</pre>



<p class="is-style-iw-2em">以上是接口的创建和实例化，书上说最好使用public void onAttach(Context context)，但是我查了资料发现我用的API22恰好就没有context，且context版的在API23以下的设备上运行不成功，所以我就直接使用onAttach(Activity activity)。</p>



<p class="is-style-iw-2em">在CrimeListActivity中实现该接口后重写void onCrimeSelected(Crime crime)方法以实现根据所选的Crime和设备屏幕大小来创建适当的视图。</p>



<pre class="wp-block-preformatted">@Override<br><strong>public void </strong>onCrimeSelected(Crime crime) {<br>    <strong>if </strong>(findViewById(R.id.<strong><em>detail_fragment_container</em></strong>)==<strong>null</strong>)<br>    {<br>        Intent intent=CrimePagerActivity.<em>newIntent</em>(<strong>this</strong>,crime.getId());<br>        startActivity(intent);<br>    }<br>    <strong>else<br></strong><strong>    </strong>{<br>        Fragment newDetail=CrimeFragment.<em>newInstance</em>(crime.getId());<br><br>        getSupportFragmentManager().beginTransaction().replace(R.id.<strong><em>detail_fragment_container</em></strong>,newDetail).commit();<br>    }<br>}</pre>



<p class="is-style-iw-2em">那么在需要创建CrimeFragment的地方只要写一句</p>



<pre class="wp-block-code"><code>mCallbacks.onCrimeSelected(crime);</code></pre>



<p class="is-style-iw-2em">就可以了。</p>



<p class="is-style-iw-2em">此外由于布局的变化导致了一些其它的问题，也可以使用回调接口来实现，例如需要在更改锅CrimeFragment的内容之后就需要立即刷新左侧的RecyclerView，这时候就可以让接口实例来完成刷新任务，实现方法是找到包含RecyclerView的fragment，调用其updateUI方法。这里寻找栈中的fragment是根据视图资源的id来寻找的。</p>



<pre class="wp-block-preformatted">@Override<br><strong>public void </strong>onCrimeUpdate(Crime crime) {<br>    CrimeListFragment listFragment=(CrimeListFragment)getSupportFragmentManager().findFragmentById(R.id.<strong><em>fragment_container</em></strong>);<br>    listFragment.updateUI();<br>}</pre>



<p class="is-style-iw-2em">之前在按下一个Crime Fragment的删除键之后，直接调用了托管activity的finish()方法，但是现在CrimeFragment是由主activity直接托管的，所以需要更改逻辑。这里同样是使用回调接口：</p>



<pre class="wp-block-preformatted">@Override
<strong>public void </strong>onCrimeDelete(Crime crime) {
    CrimeFragment crimeFragment=(CrimeFragment)getSupportFragmentManager().findFragmentById(R.id.<strong><em>detail_fragment_container</em></strong>);
    <strong>if </strong>(crimeFragment!=<strong>null</strong>)
{
getSupportFragmentManager().beginTransaction().remove(crimeFragment).commit();
}
}</pre>



<p>其中要判断获取的fragment是不是为null，如果remove的参数是null会报错：</p>



<pre class="wp-block-code"><code>NullPointerException: Attempt to write to field 'int android.support.v4.app.Fragment.mNextAnim</code></pre>



<p class="is-style-iw-2em">这个方法是实现了删去一个Crime之后将其原来的CrimeFragment也清空，同样的左侧的记录也要被删除，所以需要调用一次mCrime。</p>



<pre class="wp-block-preformatted">//CrimeFragment
<strong>public boolean </strong>onOptionsItemSelected(MenuItem item) {
    <strong>switch </strong>(item.getItemId())
    {
        <strong>case </strong>R.id.<strong><em>delete_crime</em></strong>:

            CrimeLab crimeLab=CrimeLab.<em>get</em>(getActivity());
            crimeLab.deleteCrime(<strong>mCrime</strong>);<em>
            //getActivity().finish();
            </em><strong>mCallbacks</strong>.onCrimeUpdate(<strong>mCrime</strong>);
            <strong>mCallbacks</strong>.onCrimeDelete(<strong>mCrime</strong>);
            <strong>return true</strong>;
        <strong>default</strong>:
            <strong>return super</strong>.onOptionsItemSelected(item);
    }
}</pre>



<h3 class="wp-block-heading">有关挑战练习 添加滑动删除功能</h3>



<p><a href="https://www.jianshu.com/p/a03f6a0a14a0" class="rank-math-link" target="_blank" rel="noopener">入门资料</a>，<a href="https://www.jianshu.com/p/2f3813f27236" class="rank-math-link" target="_blank" rel="noopener">一般资料</a>，<a href="https://blog.csdn.net/u014133119/article/details/80942932" class="rank-math-link" target="_blank" rel="noopener">硬核资料</a></p>



<h4 class="wp-block-heading">实现滑动功能</h4>



<p class="is-style-iw-2em">创建一个类继承ItemTouchHelper.Callback，并且与RecycleView建立链接</p>



<pre class="wp-block-preformatted"><strong>public class </strong>ItemTouchHelperCallback <strong>extends </strong>ItemTouchHelper.Callback<br>{<br><br>    @Override<br>    <strong>public int </strong>getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {<br>        <strong>final int </strong>dragFlags = ItemTouchHelper.<strong><em>UP </em></strong>| ItemTouchHelper.<strong><em>DOWN</em></strong>;<br>        <strong>final int </strong>swipeFlags = ItemTouchHelper.<strong><em>START </em></strong>| ItemTouchHelper.<strong><em>END</em></strong>;<br>        <strong>return </strong><em>makeMovementFlags</em>(0, swipeFlags);<br>    }<br><br>    @Override<br>    <strong>public boolean </strong>onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {<br>        <strong>return false</strong>;<br>    }<br><br>    @Override<br>    <strong>public void </strong>onSwiped(RecyclerView.ViewHolder viewHolder, <strong>int </strong>direction) {<br><br>        <strong>mCallbacks</strong>.onCrimeDeleteDirectly(((Holder)viewHolder).getCrime());<br>        CrimeLab crimeLab=CrimeLab.<em>get</em>(getActivity());<br>        crimeLab.deleteCrime(((Holder)viewHolder).getCrime());<br>        <strong>mAdapter</strong>.onItemDelete(viewHolder.getAdapterPosition());<br>        updateUI();<br><br>    }<br>}</pre>



<p class="is-style-iw-2em">其中第一个方法是确定可以移动的方向：上下为一组，左右为一组，按照定义的类型作为返回值；第二个方式是上下拖动时的处理；第三个是左右滑动时的处理。</p>



<h4 class="wp-block-heading">实现删除功能</h4>



<p class="is-style-iw-2em">需要定义一个接口来实现删除功能，</p>



<pre class="wp-block-preformatted"><strong>public interface </strong>ItemHelper {<br><br>    <strong>void </strong>onItemDelete(<strong>int </strong>position);<br><br>}</pre>



<p class="is-style-iw-2em">让Adapter来实现这个接口</p>



<pre class="wp-block-preformatted">@Override<br><strong>public void </strong>onItemDelete(<strong>int </strong>position) {<br>    <strong>mCrimes</strong>.remove(position);<br>    notifyItemRemoved(position);<br>}</pre>



<p class="is-style-iw-2em">用position作为参数是因为position是可以在Adapter和ItemTouchHelper.Callback中都能得到的且唯一确定需要删除的crime的位置的元素。</p>



<p class="is-style-iw-2em">所以问题就在于如何获取这个位置上的crime。我是在viewHolder中定义了一个getter，然后根据viewHolder来获取其绑定的crime。</p>



<pre class="wp-block-preformatted"><strong>private abstract class </strong>Holder <strong>extends </strong>RecyclerView.ViewHolder
{
    <strong>private </strong>Crime <strong>mCrime</strong>;

    <strong>public </strong>Crime getCrime() {
        <strong>return mCrime</strong>;
    }
   ...

}</pre>



<p class="is-style-iw-2em">所以public void onSwiped中的具体实现是</p>



<pre class="wp-block-preformatted">@Override
<strong>public void </strong>onSwiped(RecyclerView.ViewHolder viewHolder, <strong>int </strong>direction) {

<strong>mCallbacks</strong>.onCrimeDeleteDirectly(((Holder)viewHolder).getCrime());
    CrimeLab crimeLab=CrimeLab.<em>get</em>(getActivity());
    crimeLab.deleteCrime(((Holder)viewHolder).getCrime());
    <strong>mAdapter</strong>.onItemDelete(viewHolder.getAdapterPosition());
    updateUI();

}</pre>



<p class="is-style-iw-2em">其中(Holder)viewHolder).getCrime()是由于历史原因要实现的多态，mCallbacks.onCrimeDeleteDirectly的代码和mCallbacks.onCrimeDelete相同，只不过一个是CrimeListFragment中接口的方法，还有一个是CrimeFragment中的，同名可能会发生冲突，所以另外写了一个名字。</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="920" height="627" src="https://www.cztcode.com/wp-content/uploads/2020/06/GIF-2020-6-23-20-53-34.gif" alt="" class="wp-image-2704" /></figure>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cztcode.com/2020/2699/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2699</post-id>	</item>
		<item>
		<title>Intent</title>
		<link>https://www.cztcode.com/2020/2689/</link>
					<comments>https://www.cztcode.com/2020/2689/#respond</comments>
		
		<dc:creator><![CDATA[Jellow]]></dc:creator>
		<pubDate>Tue, 23 Jun 2020 06:19:23 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<guid isPermaLink="false">https://www.cztcode.com/?p=2689</guid>

					<description><![CDATA[Intent主要用于Activity间或者Fragment调用Activity时通信。Activity调用Fragment或Fragment间通讯使用argument。]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div>
<p class="is-style-iw-2em">Intent主要用于Activity间或者Fragment调用Activity时通信。Activity调用Fragment或Fragment间通讯使用argument。</p>



<h3 class="wp-block-heading">显式Intent</h3>



<p class="is-style-iw-2em">首先在想要通信的Activity内部定义newIntent方法，指定目标类（本类）。设定想要传递的参数。</p>



<figure class="wp-block-image size-large"><img decoding="async" width="784" height="136" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-96.png" alt="" class="wp-image-2692" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-96.png 784w, https://www.cztcode.com/wp-content/uploads/2020/06/image-96-300x52.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-96-768x133.png 768w" sizes="(max-width: 784px) 100vw, 784px" /></figure>



<p class="is-style-iw-2em">调用类传入参数，并使用REQUEST代码区分启动的Activity。</p>



<figure class="wp-block-image size-large"><img decoding="async" width="905" height="42" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-97.png" alt="" class="wp-image-2693" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-97.png 905w, https://www.cztcode.com/wp-content/uploads/2020/06/image-97-300x14.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-97-768x36.png 768w" sizes="(max-width: 905px) 100vw, 905px" /></figure>



<p class="is-style-iw-2em">被调用Activity使用getIntent获取传入的Intent</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="45" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-98-1024x45.png" alt="" class="wp-image-2694" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-98-1024x45.png 1024w, https://www.cztcode.com/wp-content/uploads/2020/06/image-98-300x13.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-98-768x34.png 768w, https://www.cztcode.com/wp-content/uploads/2020/06/image-98.png 1072w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="is-style-iw-2em">在被调用Activity中，定义返回数据的方法，调用返回代码。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="638" height="152" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-99.png" alt="" class="wp-image-2695" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-99.png 638w, https://www.cztcode.com/wp-content/uploads/2020/06/image-99-300x71.png 300w" sizes="(max-width: 638px) 100vw, 638px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="878" height="206" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-101.png" alt="" class="wp-image-2697" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-101.png 878w, https://www.cztcode.com/wp-content/uploads/2020/06/image-101-300x70.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-101-768x180.png 768w" sizes="(max-width: 878px) 100vw, 878px" /></figure>



<p class="is-style-iw-2em">最后，在调用类里监听。如果返回码等于REQUSET中设定的值，即可取出内容。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="904" height="346" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-100.png" alt="" class="wp-image-2696" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-100.png 904w, https://www.cztcode.com/wp-content/uploads/2020/06/image-100-300x115.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-100-768x294.png 768w" sizes="(max-width: 904px) 100vw, 904px" /></figure>



<h3 class="wp-block-heading">隐式Intent</h3>



<p class="is-style-iw-2em"><a href="https://www.cztcode.com/2020/android-learning-notes-15/" class="rank-math-link">文章链接</a></p>



<p class="is-style-iw-2em"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cztcode.com/2020/2689/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2689</post-id>	</item>
		<item>
		<title>Java安卓学习总结（二十）</title>
		<link>https://www.cztcode.com/2020/2678/</link>
					<comments>https://www.cztcode.com/2020/2678/#respond</comments>
		
		<dc:creator><![CDATA[Jellow]]></dc:creator>
		<pubDate>Mon, 22 Jun 2020 14:49:03 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<guid isPermaLink="false">https://www.cztcode.com/?p=2678</guid>

					<description><![CDATA[Java安卓学习总结（二十） 第十六章 使用Intent拍照]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div>


<h2 class="wp-block-heading">第十六章 使用Intent拍照</h2>



<h3 class="wp-block-heading">有关FileProvider</h3>



<p class="is-style-iw-2em">FileProvider可以实现从其它应用接受一个文件。需要在androidManifest中定义，例如：</p>



<pre class="wp-block-preformatted">&lt;<strong>provider<br></strong><strong>    </strong><strong>android</strong><strong>:authorities</strong><strong>="com.example.a73421.criminalintentchallenge.fileprovider"<br></strong><strong>    </strong><strong>android</strong><strong>:name</strong><strong>="android.support.v4.content.FileProvider"<br></strong><strong>    </strong><strong>android</strong><strong>:exported</strong><strong>="false"<br></strong><strong>    </strong><strong>android</strong><strong>:grantUriPermissions</strong><strong>="true"</strong>&gt;<br>    &lt;<strong>meta-data<br></strong><strong>        </strong><strong>android</strong><strong>:name</strong><strong>="android.support.FILE_PROVIDER_PATHS"<br></strong><strong>        </strong><strong>android</strong><strong>:resource</strong><strong>="@xml/files"</strong>/&gt;<br>&lt;/<strong>provider</strong>&gt;</pre>



<p class="is-style-iw-2em">其中android:name是使用的FileProvider的名称，android:authorities是你定义是一个权限，android:exported表示别的应用是否可以使用这个FileProvider，android:grantUriPermissions表示是否可以授权。</p>



<p class="is-style-iw-2em">meta-data用于关联路径描述资源，是FileProvider使用的文件地址，其中android:name是这个资源的类型，android:resource是要关联的路径描述资源。xml/files定义如下：</p>



<pre class="wp-block-preformatted">&lt;<strong>paths</strong>&gt;<br>    &lt;<strong>files-path<br></strong><strong>        </strong><strong>name</strong><strong>="crime_photo"<br></strong><strong>        </strong><strong>path</strong><strong>="."</strong>/&gt;<br>&lt;/<strong>paths</strong>&gt;</pre>



<h3 class="wp-block-heading">有关相机Intent</h3>



<p class="is-style-iw-2em">依旧是创建一个隐式的intent，操作是</p>



<pre class="wp-block-preformatted">MediaStore.<strong><em>ACTION_IMAGE_CAPTURE</em></strong></pre>



<p class="is-style-iw-2em">之后因为需要将捕获的照片存到对应路径，还要将文件路径转化为相机应用可识别的uri形式，需要用到FileProvider.getUriForFile()。FileProvider.getUriForFile()中的参数有本地路径，还有使用这个路径所需要的权限（之前在androidManifest中定义的），如：</p>



<pre class="wp-block-preformatted">Uri uri= FileProvider.<em>getUriForFile</em>(getActivity(),<strong>"com.example.a73421.criminalintentchallenge.fileprovider"</strong>,<strong>mPhotoFile</strong>);</pre>



<p class="is-style-iw-2em">将上面的uri传给相机应用的操作是</p>



<pre class="wp-block-preformatted">MediaStore.<strong><em>EXTRA_OUTPUT</em></strong>
captureImage.putExtra(MediaStore.<strong><em>EXTRA_OUTPUT</em></strong>,uri);</pre>



<p class="is-style-iw-2em">由于相机应用需要权限才能执行这个任务，所以还需要对所有相机应用暂时附加写入文件的权限。做法是遍历所有这些应用，然后依次添加：</p>



<pre class="wp-block-preformatted">List&lt;ResolveInfo&gt; cameraACtivities =getActivity().getPackageManager().queryIntentActivities(captureImage,PackageManager.<strong><em>MATCH_DEFAULT_ONLY</em></strong>);<br><br><strong>for </strong>(ResolveInfo activity:cameraACtivities)<br>{<br>    getActivity().grantUriPermission(activity.<strong>activityInfo</strong>.<strong>packageName</strong>,uri,Intent.<strong><em>FLAG_GRANT_WRITE_URI_PERMISSION</em></strong>);<br>}</pre>



<p class="is-style-iw-2em">从相机应用返回后需要再撤销那个权限：</p>



<pre class="wp-block-preformatted">Uri uri=FileProvider.<em>getUriForFile</em>(getActivity(),<strong>"com.example.a73421.criminalintentchallenge.fileprovider"</strong>,<strong>mPhotoFile</strong>);<br><br>getActivity().revokeUriPermission(uri,Intent.<strong><em>FLAG_GRANT_WRITE_URI_PERMISSION</em></strong>);</pre>



<h3 class="wp-block-heading">有关位图的压缩</h3>



<p class="is-style-iw-2em">需要用到BitmapFactory.options：</p>



<pre class="wp-block-preformatted"><strong>public class </strong>PictureUtils {<br>    <strong>public static </strong>Bitmap getScaledBitmap(String path, Activity activity)<br>    {<br>        Point size=<strong>new </strong>Point();<br>        activity.getWindowManager().getDefaultDisplay().getSize(size);<br><br>        <strong>return </strong><em>getScaledBitmap</em>(path,size.<strong>x</strong>,size.<strong>y</strong>);<br>    }<br>    <strong>public static </strong>Bitmap getScaledBitmap(String path,<strong>int </strong>destWidth,<strong>int </strong>destHeight)<br>    {<br>        BitmapFactory.Options options=<strong>new </strong>BitmapFactory.Options();<br>        options.<strong>inJustDecodeBounds</strong>=<strong>true</strong>;<br>        BitmapFactory.<em>decodeFile</em>(path,options);<br><br>        <strong>float </strong>srcWidth=options.<strong>outWidth</strong>;<br>        <strong>float </strong>srcHeight=options.<strong>outHeight</strong>;<br><br>        <strong>int </strong>inSampleSize=1;<br>        <strong>if </strong>(srcHeight&lt;destHeight||srcWidth&gt;destWidth)<br>        {<br>            <strong>float </strong>heightScale=srcHeight/destHeight;<br>            <strong>float </strong>widthScale=srcWidth/destWidth;<br><br>            inSampleSize=Math.<em>round</em>(heightScale&gt;widthScale?heightScale:widthScale);<br>        }<br><br>        options=<strong>new </strong>BitmapFactory.Options();<br>        options.<strong>inSampleSize</strong>=inSampleSize;<br><br>        <strong>return </strong>BitmapFactory.<em>decodeFile</em>(path,options);<br>    }<br>}</pre>



<p class="is-style-iw-2em">一些参数说明：</p>



<p class="is-style-iw-2em">injustDecodeBounds设为true，那么BitmapFactory并不会真的返回一个Bitmap给你，它仅仅会把它的宽，高取回来给你，这样就不会占用太多的内存，也就不会那么频繁的发生OOM。设置为false，BitmapFactory返回bitmap。</p>



<p class="is-style-iw-2em">outWidth&amp;outHeight是bitmap图像的宽和高；</p>



<p class="is-style-iw-2em">inSampleSize;获取采样率<br>inSampleSize大于1时，图像高、宽分别以2的inSampleSize次方分之一缩小<br>inSampleSize小于等于1时，图像高、宽不变；</p>



<p class="is-style-iw-2em">所以getScaledBitmap方法干了这么一件事：先把图像的宽和高取回来，分别除一下应用显示区域的宽和高，得到压缩比例，再按照缩放比例新建一个bitmap返回。</p>



<h3 class="wp-block-heading">有关挑战练习 优化照片显示</h3>



<p class="is-style-iw-2em">做法就和TimePicker过程一样，需要新建一个DialogFragment和相关的.xml文件。</p>



<p class="is-style-iw-2em">给显示缩略图的mPhotoView增加有关监听器，被按下时创建一个dialog：</p>



<pre class="wp-block-preformatted"><strong>mPhotoView</strong>.setOnClickListener(<strong>new </strong>OnClickListener() {
    @Override
    <strong>public void </strong>onClick(View v) {
        Toast.<em>makeText</em>(getActivity(),<strong>mPhotoFile</strong>.toString(),Toast.<strong><em>LENGTH_SHORT</em></strong>).show();
        FragmentManager manager=getFragmentManager();
        ShowPictureFragment dialog=ShowPictureFragment.<em>newInstance</em>(<strong>mPhotoFile</strong>);<em>
        </em>dialog.show(manager,<strong><em>DIALOG_SHOW_PICTURE</em></strong>);
    }
});</pre>



<p class="is-style-iw-2em">其中创建ShowPictureFragment时传入的是要显示的图片的位置。在ShowPictureFragment的onCreateDialog方法中要获取这个图片的位置将其转化为ImageVIew可识别的uri，并且绑定到ImageView中：</p>



<pre class="wp-block-preformatted"><strong>public </strong>Dialog onCreateDialog(Bundle savedInstanceState) {
    View v= LayoutInflater.<em>from</em>(getActivity()).inflate(R.layout.<strong><em>dialog_picture_show</em></strong>,<strong>null</strong>);

    File file=(File)getArguments().getSerializable(<strong><em>ARG_PICTURE</em></strong>);
    Uri uri = Uri.<em>fromFile</em>(file);

    <strong>mPictureImageView</strong>=(ImageView)v.findViewById(R.id.<strong><em>show_picture</em></strong>);
    <strong>mPictureImageView</strong>.setImageURI(uri);
    
<strong>return new </strong>AlertDialog.Builder(getActivity()).
            setView(v).
            setPositiveButton(android.R.string.<strong><em>ok</em></strong>,<strong>null</strong>).
            setTitle(R.string.<strong><em>show_picture</em></strong>).
            create();
}</pre>



<p class="is-style-iw-2em">ImageView有四种指定图片的方法，这里我用的是uri，查看<a href="https://www.jianshu.com/p/c1920afda50e" class="rank-math-link" target="_blank" rel="noopener">其它用法</a>。</p>



<h3 class="wp-block-heading">有关挑战练习 优化缩略图加载</h3>



<p class="is-style-iw-2em">这个题目我原以为是优化缩略图的生成效率，实际上是优化缩略图的加载时机，即只有当其布局文件发生变化之后再进行加载。要用到的ViewTreeObserver就可以监听布局的传递。</p>



<pre class="wp-block-preformatted">ViewTreeObserver observer = <strong>mPhotoView</strong>.getViewTreeObserver();<br>observer.addOnGlobalLayoutListener(<strong>new </strong>ViewTreeObserver.OnGlobalLayoutListener() {<br>    @Override<br>    <strong>public void </strong>onGlobalLayout() {<br>        <strong>mPhotoView</strong>.setEnabled(<strong>mPhotoFile</strong>.exists());<br>        updatePhotoView();<br>    }<br>});</pre>



<p class="is-style-iw-2em">同样的也可以在里面设置mPhotoView是否可用，当路径不存在照片时就禁用mPhotoView。</p>



<p class="is-style-iw-2em">ViewTreeObserver的六个接口类的<a href="https://blog.csdn.net/bobxie520/article/details/47045267" class="rank-math-link" target="_blank" rel="noopener">参考</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cztcode.com/2020/2678/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2678</post-id>	</item>
		<item>
		<title>Android学习笔记(十五)</title>
		<link>https://www.cztcode.com/2020/2666/</link>
					<comments>https://www.cztcode.com/2020/2666/#comments</comments>
		
		<dc:creator><![CDATA[Jellow]]></dc:creator>
		<pubDate>Mon, 22 Jun 2020 14:03:57 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<guid isPermaLink="false">https://www.cztcode.com/?p=2666</guid>

					<description><![CDATA[本章主要讲了隐式Intent和字符串拼接]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div>
<p class="is-style-iw-2em">本章主要讲了<strong>隐式Intent</strong>和<strong>字符串拼接</strong></p>



<h2 class="wp-block-heading">本章总结</h2>



<h3 class="wp-block-heading">隐式Intent</h3>



<h5 class="wp-block-heading">打开隐式Intent</h5>



<p class="is-style-iw-2em">通过隐式Intent可以让操作系统寻找适用的Activity。</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-84.png" alt="" class="wp-image-2667"/></figure>



<p class="is-style-iw-2em">简略形式</p>



<pre class="wp-block-code"><code> Intent i = ShareCompat.IntentBuilder.from(getActivity()).setType("text/plain").setText(getCrimeReport()).getIntent();//简略表达</code></pre>



<p class="is-style-iw-2em">更多系统支持的Activity 点击 <a href="https://developer.android.com/guide/components/intents-common?hl=zh-cn" class="rank-math-link" target="_blank" rel="noopener">官方文档</a></p>



<h5 class="wp-block-heading">从隐式Intent取回数据</h5>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-85-1024x162.png" alt="" class="wp-image-2668" width="580" height="91"/></figure>



<p class="is-style-iw-2em">查询联系人列表返回一个uri对象，uri即为可标识的资源。查找的联系人就是一个资源，我们先新建查找目标的数组，这里只查询一个NAME。建立游标，游标中第一行即为查询的结果（只有一个数据）。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="359" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-86-1024x359.png" alt="" class="wp-image-2669" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-86-1024x359.png 1024w, https://www.cztcode.com/wp-content/uploads/2020/06/image-86-300x105.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-86-768x269.png 768w, https://www.cztcode.com/wp-content/uploads/2020/06/image-86.png 1085w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h5 class="wp-block-heading">检查是否有支持的Activity</h5>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="980" height="156" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-87.png" alt="" class="wp-image-2670" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-87.png 980w, https://www.cztcode.com/wp-content/uploads/2020/06/image-87-300x48.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-87-768x122.png 768w" sizes="(max-width: 980px) 100vw, 980px" /></figure>



<h5 class="wp-block-heading">举例：调用拨号界面</h5>



<pre class="wp-block-code"><code>Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);</code></pre>



<h3 class="wp-block-heading">字符串拼接</h3>



<p class="is-style-iw-2em">使用%1$s %2$s对应动态字符串的第一个位置和第二个位置</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="404" height="62" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-88.png" alt="" class="wp-image-2671" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-88.png 404w, https://www.cztcode.com/wp-content/uploads/2020/06/image-88-300x46.png 300w" sizes="(max-width: 404px) 100vw, 404px" /></figure>



<h5 class="wp-block-heading">属性值举例说明</h5>



<ul class="wp-block-list"><li>%n$ms：代表输出的是字符串，n代表是第几个参数，设置m的值可以在输出之前放置空格&nbsp;</li><li>%n$md：代表输出的是整数，n代表是第几个参数，设置m的值可以在输出之前放置空格，也可以设为0m,在输出之前放置m个0&nbsp;</li><li>%n$mf：代表输出的是浮点数，n代表是第几个参数，设置m的值可以控制小数位数，如m=2.2时，输出格式为00.00&nbsp;</li></ul>



<p class="is-style-iw-2em">使用getStrng(动态字符串，字符串1，字符串2&#8230;) 可完成绑定</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="753" height="372" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-89.png" alt="" class="wp-image-2672" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-89.png 753w, https://www.cztcode.com/wp-content/uploads/2020/06/image-89-300x148.png 300w" sizes="(max-width: 753px) 100vw, 753px" /></figure>



<h2 class="wp-block-heading">挑战练习</h2>



<h3 class="wp-block-heading">ShareCompat</h3>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="190" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-90-1024x190.png" alt="" class="wp-image-2673" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-90-1024x190.png 1024w, https://www.cztcode.com/wp-content/uploads/2020/06/image-90-300x56.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-90-768x143.png 768w, https://www.cztcode.com/wp-content/uploads/2020/06/image-90.png 1104w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h4 class="wp-block-heading">又一个隐式intent</h4>



<p class="is-style-iw-2em">添加一个按钮布局，然后在数据库中根据联系人姓名查表，这里注意查的事CommonDataKinds.Phone表</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="767" height="436" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-92.png" alt="" class="wp-image-2676" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-92.png 767w, https://www.cztcode.com/wp-content/uploads/2020/06/image-92-300x171.png 300w" sizes="(max-width: 767px) 100vw, 767px" /></figure>



<p class="is-style-iw-2em">注意添加权限授权</p>



<pre class="wp-block-code"><code>&lt;uses-permission android:name="android.permission.READ_CONTACTS"/></code></pre>



<p class="is-style-iw-2em">但是添加完也不够，手机默认拒绝你的权限，手动在应用信息界面允许权限。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="366" height="390" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-91.png" alt="" class="wp-image-2675" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-91.png 366w, https://www.cztcode.com/wp-content/uploads/2020/06/image-91-282x300.png 282w" sizes="(max-width: 366px) 100vw, 366px" /></figure>



<p class="is-style-iw-2em">显示效果</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.cztcode.com/wp-content/uploads/2020/06/14.gif" alt="" class="wp-image-2680"/></figure>



<p class="is-style-iw-2em"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cztcode.com/2020/2666/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2666</post-id>	</item>
		<item>
		<title>Java安卓学习总结（十九）</title>
		<link>https://www.cztcode.com/2020/2664/</link>
					<comments>https://www.cztcode.com/2020/2664/#comments</comments>
		
		<dc:creator><![CDATA[Jellow]]></dc:creator>
		<pubDate>Sun, 21 Jun 2020 15:26:39 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<guid isPermaLink="false">https://www.cztcode.com/?p=2664</guid>

					<description><![CDATA[Java安卓学习总结（十九） 第十五章 隐式intent]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div>


<h2 class="wp-block-heading">第十五章 隐式intent</h2>



<p class="is-style-iw-2em">有关格式化字符串</p>



<p class="is-style-iw-2em">之前用过复数字符串，是根据所给参数而确定使用的字符串内容。而格式化字符串的参数就是直接填补了字符串内的格式字符。</p>



<p class="is-style-iw-2em">定义方法如下：</p>



<pre class="wp-block-preformatted">&lt;<strong>string </strong><strong>name</strong><strong>="crime_report"</strong>&gt;%1$s!<br> The crime was discovered on %2$s. %3$s, and %4$s&lt;/<strong>string</strong>&gt;</pre>



<p class="is-style-iw-2em">其中形如%1$s等是占位符。</p>



<h3 class="wp-block-heading">有关隐式intent</h3>



<p class="is-style-iw-2em">最根本的区别是构造的方法不同。显式intent的两个参数分别是当前Activity和需要创建的Activity类，如</p>



<pre class="wp-block-code"><code>Intent intent=new Intent(getActivity(),CrimePagerActivity.class)</code></pre>



<p class="is-style-iw-2em">而隐式intent的参数有很多，依次是要执行的操作、待访问数据的位置、操作涉及的数据类型、可选类别。这些参数只有第一个是必需的，但是加上不同的参数可以解决不同的问题。</p>



<p class="is-style-iw-2em">如只写一个参数的，发送一段文本信息，操作是ACTION_SEND，其不需要访问数据。可以设置自己发送的消息的数据类型。</p>



<pre class="wp-block-code"><code>Intent i=new Intent(Intent.ACTION_SEND);
i.setType("text/plain");</code></pre>



<p class="is-style-iw-2em">再如写前两个参数的，获取联系人信息，操作是Intent.ACTION_PICK，访问的数据的位置是ContactsContract.Contacts.CONTENT_URI。</p>



<pre class="wp-block-preformatted"><strong>final </strong>Intent pickContact=<strong>new </strong>Intent(Intent.<strong><em>ACTION_PICK</em></strong>, ContactsContract.Contacts.<strong><em>CONTENT_URI</em></strong>);</pre>



<h3 class="wp-block-heading">有关activity选择器</h3>



<p class="is-style-iw-2em">有时候使用隐式intent时可能会看不见候选activity列表，原因有两个：一是已经选择了默认的响应应用，二是只有一个activity可以响应该隐式intent。</p>



<p class="is-style-iw-2em">利用Intent.createChooser()可以创建每次都显示的选择器。</p>



<pre class="wp-block-preformatted">Intent i= ShareCompat.IntentBuilder.<em>from</em>(getActivity()).setType(<strong>"text/plain"</strong>).getIntent();<br>i.putExtra(Intent.<strong><em>EXTRA_TEXT</em></strong>,getCrimeReport());<br>i.putExtra(Intent.<strong><em>EXTRA_SUBJECT</em></strong>,getString(R.string.<strong><em>crime_report_subject</em></strong>));<br>i=Intent.<em>createChooser</em>(i,getString(R.string.<strong><em>send_report</em></strong>));<br>startActivity(i);</pre>



<h3 class="wp-block-heading">有关ContentProvider类</h3>



<p class="is-style-iw-2em">ContentProvider是Android深度定制了一个用于处理联系人信息的API，其将联系人数据库的信息封装起来供其他应用使用。利用ContentResolver来访问ContentProvider的内容。</p>



<pre class="wp-block-preformatted">Cursor c=getActivity().getContentResolver().query(contactUri,queryField,<strong>null</strong>,<strong>null</strong>,<strong>null</strong>);</pre>



<p class="is-style-iw-2em">因为是定制的，所以query的五个参数略有不同，第一个是访问的位置（和表名相关），第二个是要查询的列（投影），第三个是where语句，第四个是where语句的参数（如果有），且全都放在一个string数组里面，第五个是排序方式orderby。</p>



<p class="is-style-iw-2em">返回的是一个游标，从游标中读取数据的操作照常。</p>



<h3 class="wp-block-heading">有关应用权限</h3>



<p class="is-style-iw-2em">获取联系人姓名是在ContactsContract.Contacts.CONTENT_URI中查找的，可能是因为里面的信息不多，且很安全，所以联系人应用就可以暂时将权限赋给我们使用。具体实现是联系人应用返回时给intent添加了一个</p>



<pre class="wp-block-code"><code>Intent.FLAG_GRANT_READ_URI_PERMISSION</code></pre>



<p class="is-style-iw-2em">让应用可以暂时读取联系人数据一次。</p>



<h3 class="wp-block-heading">有关检查可响应任务的activity</h3>



<p class="is-style-iw-2em">通过操作系统中的PackageManager类进行自检，在onCreateView中进行检查。</p>



<pre class="wp-block-preformatted">PackageManager packageManager=getActivity().getPackageManager();<br><strong>if </strong>(packageManager.resolveActivity(pickContact,PackageManager.<strong><em>MATCH_DEFAULT_ONLY</em></strong>)==<strong>null</strong>)<br>{<br>    <strong>mSuspectButton</strong>.setEnabled(<strong>false</strong>);<br>}</pre>



<p class="is-style-iw-2em">还可以添加一个没什么用的操作给intent，来验证是否有作用。</p>



<pre class="wp-block-preformatted"><em>pickContact.addCategory(Intent.CATEGORY_HOME);</em></pre>



<h3 class="wp-block-heading">有关挑战练习 ShareCompat</h3>



<p class="is-style-iw-2em">ShareCompat.IntentBuilder也是一个Builder，也可以通过流接口来定义。</p>



<pre class="wp-block-preformatted">Intent i= ShareCompat.IntentBuilder.<em>from</em>(getActivity()).setType(<strong>"text/plain"</strong>).getIntent();</pre>



<h3 class="wp-block-heading">有关挑战练习 又一个隐式Intent</h3>



<p class="is-style-iw-2em">首先是要先查到电话号码，还是使用getContentResolver()，只不过这一次查询的位置是ContactsContract.CommonDataKinds.Phone.CONTENT_URI。</p>



<p class="is-style-iw-2em">这一次要权限了，不加权限回崩溃！</p>



<pre class="wp-block-code"><code>java.lang.SecurityException: Permission Denial: opening provider com.android.providers.contacts.ContactsProvider2 from ProcessRecord{22ec7a83 26970:com.example.a73421.criminalintentchallenge/u0a63} (pid=26970, uid=10063) requires android.permission.READ_CONTACTS or android.permission.WRITE_CONTACTS</code></pre>



<p class="is-style-iw-2em">在AndroidManifest.xml中加入android.permission.READ_CONTACTS这个permission，<a href="https://m.jb51.net/article/76380.htm" class="rank-math-link" target="_blank" rel="noopener">参考资料</a>，代码如下：</p>



<pre class="wp-block-code"><code>&lt;uses-permission android:name="android.permission.READ_CONTACTS" /&gt;</code></pre>



<p class="is-style-iw-2em">获取电话号码：</p>



<pre class="wp-block-preformatted">String suspectName=<strong>mCrime</strong>.getSuspect();<br>String[] queryField=<strong>new </strong>String[]{ContactsContract.CommonDataKinds.Phone.<strong><em>NUMBER</em></strong>};<br>Cursor cursor= getActivity().getContentResolver().query(<br>        ContactsContract.CommonDataKinds.Phone.<strong><em>CONTENT_URI</em></strong>,<br>        queryField,<br>        ContactsContract.CommonDataKinds.Phone.<strong><em>DISPLAY_NAME </em></strong>+ <strong>"=?"</strong>,<br>        <strong>new </strong>String[]{suspectName},<br>        <strong>null</strong>);<br><strong>if </strong>(cursor.getCount()==0)<br>{<br>    <strong>return</strong>;<br>}<br>cursor.moveToFirst();<br><strong>int </strong>indexPhone=cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.<strong><em>NUMBER</em></strong>);<br>String suspectNumber=cursor.getString(indexPhone);</pre>



<p class="is-style-iw-2em">之后是用所得电话号码构造URI：</p>



<pre class="wp-block-code"><code>Uri number=Uri.parse("tel:"+suspectNumber);</code></pre>



<p class="is-style-iw-2em">启动activity：</p>



<pre class="wp-block-preformatted">Intent pickPhone=<strong>new </strong>Intent(Intent.<strong><em>ACTION_DIAL</em></strong>,number);<br>startActivity(pickPhone);</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cztcode.com/2020/2664/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2664</post-id>	</item>
		<item>
		<title>SQLite基本使用</title>
		<link>https://www.cztcode.com/2020/2636/</link>
					<comments>https://www.cztcode.com/2020/2636/#respond</comments>
		
		<dc:creator><![CDATA[Jellow]]></dc:creator>
		<pubDate>Sun, 21 Jun 2020 13:10:15 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<guid isPermaLink="false">https://www.cztcode.com/?p=2636</guid>

					<description><![CDATA[Android中使用SQLite数据库存储数据，重点理解CRUD使用。]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div>
<p class="is-style-iw-2em">Android中使用SQLite数据库存储数据，重点理解CRUD使用。</p>



<h2 class="wp-block-heading">SQLite简介</h2>



<h3 class="wp-block-heading">SQLite优点</h3>



<ol class="wp-block-list"><li><strong>轻量级</strong>： 使用 SQLite 只需要带一个动态库，就可以享受它的全部功能，而且那个动态库的尺寸很小。</li><li><strong>独立性</strong>： SQLite 数据库的核心引擎不需要依赖第三方软件，也不需要所谓的“安装”。</li><li><strong>隔离性</strong>：SQLite 数据库中所有的信息（比如表、视图、触发器等）都包含在一个文件夹内，方便管理和维护。</li><li><strong>跨平台</strong>：SQLite 目前支持大部分操作系统，不至电脑操作系统更在众多的手机系统也是能够运行，比如：Android和IOS。</li><li><strong>多语言接口</strong>：SQLite 数据库支持多语言编程接口。</li><li><strong>安全性</strong>：SQLite 数据库通过数据库级上的独占性和共享锁来实现独立事务处理。这意味着多个进程可以在同一时间从同一数据库读取数据，但只能有一个可以写入数据。</li><li><strong>弱类型的字段</strong>：同一列中的数据可以是不同类型</li></ol>



<h3 class="wp-block-heading">SQLite数据类型</h3>



<p class="is-style-default">SQLite具有以下五种常用的数据类型：</p>



<figure class="wp-block-table"><table><thead><tr><th>存储类</th><th>存储类</th></tr></thead><tbody><tr><td>NULL</td><td>值是一个 NULL 值</td></tr><tr><td>INTEGER</td><td>值是一个带符号的整数，根据值的大小存储在 1、2、3、4、6 或 8 字节中</td></tr><tr><td>REAL</td><td>值是一个浮点值，存储为 8 字节的 IEEE 浮点数字</td></tr><tr><td>TEXT</td><td>值是一个文本字符串，使用数据库编码（UTF-8、UTF-16BE 或 UTF-16LE）存储</td></tr><tr><td>BLOB</td><td>值是一个 blob 数据，完全根据它的输入存储</td></tr></tbody></table></figure>



<p class="is-style-default"><strong>Boolean 数据类型</strong></p>



<p class="is-style-iw-2em">SQLite 没有单独的 Boolean 存储类。相反，布尔值被存储为整数 0（false）和 1（true）。</p>



<p class="is-style-default"><strong>Date 与 Time 数据类型</strong></p>



<p class="is-style-iw-2em">SQLite 没有一个单独的用于存储日期和/或时间的存储类，但 SQLite 能够把日期和时间存储为 TEXT、REAL 或 INTEGER 值。</p>



<h2 class="wp-block-heading">SQLite使用</h2>



<h3 class="wp-block-heading">1.定义schema</h3>



<p class="is-style-iw-2em"><strong>schema</strong>就是<strong>数据库</strong>对象的集合，这个集合包含了各种对象如：表、视图、存储过程、索引等。</p>



<h4 class="wp-block-heading">操作步骤</h4>



<p class="is-style-iw-2em">首先创建一个模式，这里新建了一个CrimeDbSchema。</p>



<p class="is-style-iw-2em">按照逻辑关系，我们创建了一个名为CrimeTable的内部类作为表，定义表名。</p>



<p class="is-style-iw-2em">在表中定义 <strong>型</strong>（表头），创建内部类Cols表示列。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="871" height="454" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-63.png" alt="" class="wp-image-2637" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-63.png 871w, https://www.cztcode.com/wp-content/uploads/2020/06/image-63-300x156.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-63-768x400.png 768w" sizes="(max-width: 871px) 100vw, 871px" /></figure>



<h3 class="wp-block-heading">2.定义初始数据库</h3>



<p class="is-style-default">创建一个类继承SQLiteOpenHelper，需要实现其三个方法：</p>



<ul class="wp-block-list"><li>构造方法：需要给SQLiteOpenHelper传递四个参数:(上下文，数据库名，游标工厂(通常为null)，当前数据库版本号)；</li><li>onCreate(): 表的创建，初始化操作</li><li>onUpgrade(): 表升级</li></ul>



<p class="is-style-iw-2em">创建自定义class继承<strong>SQLiteOpenHelper</strong>，介绍在图中。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="965" height="470" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-66.png" alt="" class="wp-image-2640" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-66.png 965w, https://www.cztcode.com/wp-content/uploads/2020/06/image-66-300x146.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-66-768x374.png 768w" sizes="(max-width: 965px) 100vw, 965px" /></figure>



<p class="is-style-iw-2em">主码就是：<strong>_id</strong></p>



<h5 class="wp-block-heading">关于自动递增字段：</h5>



<p class="is-style-iw-2em">SQLite 的&nbsp;<strong>AUTOINCREMENT</strong>&nbsp;是一个关键字，用于表中的字段值自动递增。我们可以在创建表时在特定的列名称上使用&nbsp;<strong>AUTOINCREMENT</strong>&nbsp;关键字实现该字段值的自动增加。</p>



<p class="is-style-iw-2em">关键字 <strong>AUTOINCREMENT</strong> 只能用于整型（INTEGER）字段。<a href="https://www.runoob.com/sqlite/sqlite-autoincrement.html" class="rank-math-link" target="_blank" rel="noopener">详细介绍</a></p>



<h5 class="wp-block-heading">关于onUpgrade方法：</h5>



<p class="is-style-default">虽然暂时用不上，但这个方法是用于添加表或者列的</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-64.png" alt="" class="wp-image-2638" width="344" height="345" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-64.png 421w, https://www.cztcode.com/wp-content/uploads/2020/06/image-64-300x300.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-64-150x150.png 150w" sizes="(max-width: 344px) 100vw, 344px" /></figure>



<h4 class="wp-block-heading">创建数据库遵循的方法</h4>



<ol class="wp-block-list"><li>确认目标数据库是否存在</li><li>若不存在，首先创建数据库，然后创建数据表并初始化数据</li><li>若存在，检查Schema是否为最新版本</li><li>如果是旧版本，就先升级到最新版本</li></ol>



<h3 class="wp-block-heading">3.创建数据库</h3>



<p class="is-style-iw-2em">介绍在注释里</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="775" height="319" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-67.png" alt="" class="wp-image-2641" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-67.png 775w, https://www.cztcode.com/wp-content/uploads/2020/06/image-67-300x123.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-67-768x316.png 768w" sizes="(max-width: 775px) 100vw, 775px" /></figure>



<h4 class="wp-block-heading">写入数据</h4>



<h5 class="wp-block-heading"><strong>ContentValues</strong>类简介</h5>



<p class="is-style-iw-2em"><strong>ContentValues</strong>和Hash Table都是一种存储的机制。两者的区别在于，contentValues只能存储基本类型的数据，String，int之类的，不能存储对象，只能用于SQLite中，而Hash Table却可以存储对象。</p>



<p class="is-style-iw-2em">把数据插入数据库中时，首先要有一个ContentValues的对象：</p>



<pre class="wp-block-code"><code>ContentValues contentValues  = new ContentValues();
contentValues.put(key,values);
SQLiteDataBase sdb;
sdb.insert(database_name,null,initialValues);</code></pre>



<p class="is-style-iw-2em">成功插入则返回记录的id，否则返回-1。</p>



<h5 class="wp-block-heading">建立键值对对应关系</h5>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="814" height="169" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-68.png" alt="" class="wp-image-2642" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-68.png 814w, https://www.cztcode.com/wp-content/uploads/2020/06/image-68-300x62.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-68-768x159.png 768w" sizes="(max-width: 814px) 100vw, 814px" /></figure>



<p class="is-style-iw-2em">将Crime的值根据ContentValues添加的对应关系添加进数据库中</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="588" height="86" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-69.png" alt="" class="wp-image-2644" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-69.png 588w, https://www.cztcode.com/wp-content/uploads/2020/06/image-69-300x44.png 300w" sizes="(max-width: 588px) 100vw, 588px" /></figure>



<h4 class="wp-block-heading">更新数据</h4>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="995" height="107" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-70.png" alt="" class="wp-image-2645" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-70.png 995w, https://www.cztcode.com/wp-content/uploads/2020/06/image-70-300x32.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-70-768x83.png 768w" sizes="(max-width: 995px) 100vw, 995px" /></figure>



<p class="is-style-iw-2em">我翻译一下：传入要更新的对象。获取对象类型与数据库<strong>型</strong>之间的对应关系，也就是ContentValues。还要将待查询的UUID转化为String类型，因为SQLlite里不存UUID类型的数据。</p>



<h5 class="wp-block-heading">重点：防止SQL注入（我感觉是）</h5>



<p class="is-style-iw-2em">关于SQL注入我写过两篇文章，有兴趣可以先去看看</p>



<ul class="wp-block-list"><li><a href="https://www.cztcode.com/2020/sqlmap/" class="rank-math-link">SQLmap</a>    //黑客工具，入侵数据库</li><li><a href="https://www.cztcode.com/2020/sql-injection/" class="rank-math-link">SQL注入</a>   //Java中的SQL注入</li></ul>



<p class="is-style-iw-2em">SQLite的防止SQL注入方式和Java相同，使用？代替实际值。当数据库执行时，非？的语句会被编译。在编译后再将？处的值引入完成查询。这样有效避免了查询这种</p>



<pre class="wp-block-code"><code>select count(1) from students where name='张三' or '1=1' </code></pre>



<p class="is-style-iw-2em">会把整个数据库都显示出来的情形，因为？处语句根本没有编译执行。</p>



<div class="wp-block-image"><figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="434" height="50" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-71.png" alt="" class="wp-image-2646" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-71.png 434w, https://www.cztcode.com/wp-content/uploads/2020/06/image-71-300x35.png 300w" sizes="(max-width: 434px) 100vw, 434px" /><figcaption>这里使用数组，因为可能由多个问号依次对应</figcaption></figure></div>



<p class="is-style-iw-2em">ps：有人可能会问，insert的时候怎么不防止sql注入？我感觉可以，可能书上没写。调用insert和update都是等于系统再去调用数据库的方法，拼接上where等语句。去查了下源码，到这里就进不去了，但是确实没有对于？的处理，应该是封装在SQLite的内部。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="701" height="262" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-72.png" alt="" class="wp-image-2647" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-72.png 701w, https://www.cztcode.com/wp-content/uploads/2020/06/image-72-300x112.png 300w" sizes="(max-width: 701px) 100vw, 701px" /></figure>



<p class="is-style-iw-2em">在网上查到一种防止intsert SQL注入的方式，和update原理相同。</p>



<pre class="wp-block-code"><code>  String sql = " insert into " + TABLE_NAME + "(message,time) values(?,?)";
        Object&#91;] args = {message,time};
        db.execSQL(sql,args);
        dbClose();</code></pre>



<h5 class="wp-block-heading">调用updateCrime</h5>



<p class="is-style-iw-2em">不要忘记在onPause处添加更新语句</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-74.png" alt="" class="wp-image-2649"/></figure>



<p class="is-style-iw-2em">其实书中之前说过，当一个Fragment不可见时调用onPause和onStop。为了保险起见（onStop有可能不调用，但onPause一定调用），写在了onPause里，关于Fragment的生命周期，我也有过一篇文章说明：<a href="https://www.cztcode.com/2020/life-cycle-of-activity-and-fragment/" class="rank-math-link">Activity与Fragment的生命周期</a></p>



<p class="is-style-iw-2em">使用logcat看实际调用情况，发现了一个很有趣的地方。FragmentManger会保留最近两个的Fragment，并在当前Fragment不可见时。为当前Fragment和之前保存的一个Fragment共同调用onStop。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="518" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-73-1024x518.png" alt="" class="wp-image-2648" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-73-1024x518.png 1024w, https://www.cztcode.com/wp-content/uploads/2020/06/image-73-300x152.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-73-768x388.png 768w, https://www.cztcode.com/wp-content/uploads/2020/06/image-73.png 1451w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h4 class="wp-block-heading">查询数据</h4>



<p class="is-style-iw-2em">我们需要使用cursor（游标），通过游标帮助我们查询。</p>



<h5 class="wp-block-heading">继承CursorWrapper类</h5>



<p class="is-style-iw-2em">首先继承这个类，添加要从哪些列取得哪些数据，并返回一个实例。注意此处的getString和getLong是CursorWrapper内的方法，用于返回对于列号中的数据。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1011" height="460" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-76.png" alt="" class="wp-image-2652" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-76.png 1011w, https://www.cztcode.com/wp-content/uploads/2020/06/image-76-300x136.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-76-768x349.png 768w" sizes="(max-width: 1011px) 100vw, 1011px" /></figure>



<p class="is-style-iw-2em">在CRUD（CrimeLab）中调用初始化Cursor。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="651" height="245" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-78.png" alt="" class="wp-image-2654" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-78.png 651w, https://www.cztcode.com/wp-content/uploads/2020/06/image-78-300x113.png 300w" sizes="(max-width: 651px) 100vw, 651px" /></figure>



<p class="is-style-iw-2em">这样我们在查询时，只需使用CrimeCursorWrapper实例化出一个cursor，再利用cursor的循环操作就可以遍历出所有信息了。</p>



<h5 class="wp-block-heading">例子：获取全部列表信息</h5>



<p class="is-style-iw-2em">注意，在执行到CrimeCursorWrapper这句时数据已经在SQLite中查询完了，下面使用move等方法都是对cursor内查询到的数据的操作。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="779" height="307" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-77.png" alt="" class="wp-image-2653" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-77.png 779w, https://www.cztcode.com/wp-content/uploads/2020/06/image-77-300x118.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-77-768x303.png 768w" sizes="(max-width: 779px) 100vw, 779px" /></figure>



<h5 class="wp-block-heading">例子：根据uuid获取信息</h5>



<p class="is-style-iw-2em">这里uuid就是相当与主码，因为具有唯一性。（实际主码是<strong>_id</strong>前面说了）</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="588" height="311" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-79.png" alt="" class="wp-image-2655" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-79.png 588w, https://www.cztcode.com/wp-content/uploads/2020/06/image-79-300x159.png 300w" sizes="(max-width: 588px) 100vw, 588px" /></figure>



<h5 class="wp-block-heading">刷新模型层数据</h5>



<p class="is-style-iw-2em">为什么要干这个事呢，因为当你对数据库一顿操作出来并保存后。发现原来的视图并没有更新。因为数据不可能在使用时都去数据库取，肯定有缓存。比如我建立了一个数组保存从数据库取出来的数据，它的初始化是在视图启动时。当我数据库更新后这个数组并没有变，所以我们需要更新UI。</p>



<p class="is-style-iw-2em">我写一个set方法</p>



<pre class="wp-block-code"><code>  public void setCrimes(List&lt;Crime> crimes){
            mCrimes =crimes;
        }</code></pre>



<p class="is-style-iw-2em">这里重新更新当前的Crime数组</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="601" height="247" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-82.png" alt="" class="wp-image-2658" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-82.png 601w, https://www.cztcode.com/wp-content/uploads/2020/06/image-82-300x123.png 300w" sizes="(max-width: 601px) 100vw, 601px" /></figure>



<p class="is-style-iw-2em">这个updateUI是在Fragment运行时调用的，写在了onResume里。同时在onCreateView中也会调用（ViewHolder和Adapter绑定）。</p>



<h4 class="wp-block-heading">删除数据</h4>



<p class="is-style-iw-2em">删除很简单，和更新的方式类似。使用主码或者候选码找到项目调用delete删除，注意防止sql注入。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="813" height="90" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-83.png" alt="" class="wp-image-2659" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-83.png 813w, https://www.cztcode.com/wp-content/uploads/2020/06/image-83-300x33.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-83-768x85.png 768w" sizes="(max-width: 813px) 100vw, 813px" /></figure>



<h3 class="wp-block-heading">实现效果</h3>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="354" height="671" src="https://www.cztcode.com/wp-content/uploads/2020/06/13.gif" alt="" class="wp-image-2662"/></figure>



<p class="is-style-iw-2em"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cztcode.com/2020/2636/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2636</post-id>	</item>
		<item>
		<title>Java安卓学习总结（十八）</title>
		<link>https://www.cztcode.com/2020/2626/</link>
					<comments>https://www.cztcode.com/2020/2626/#respond</comments>
		
		<dc:creator><![CDATA[Jellow]]></dc:creator>
		<pubDate>Sat, 20 Jun 2020 13:13:57 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<guid isPermaLink="false">https://www.cztcode.com/?p=2626</guid>

					<description><![CDATA[Java安卓学习总结（十八）第十四章 SQLite数据库]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div>


<h2 class="wp-block-heading">第十四章 SQLite数据库</h2>



<h3 class="wp-block-heading">有关新建的三个数据库类</h3>



<pre class="wp-block-preformatted"><strong>public class </strong>CrimeDbSchema
<strong>public class </strong>CrimeBaseHelper <strong>extends </strong>SQLiteOpenHelper
public class CrimeCursorWrapper extends CursorWrapper</pre>



<p class="is-style-iw-2em">public class CrimeDbSchema是用于定义数据库的scheme，包括表名、属性项名。</p>



<pre class="wp-block-code"><code>public class CrimeDbSchema {
    public static final class CrimeTable
    {
        public static final String NAME="crimes";
        public static final class Cols
        {
            public static final String UUID="uuid";
            public static final String TITLE="title";
            public static final String DATE="date";
            public static final String SOLVED="solved";

        }
    }
}</code></pre>



<p class="is-style-iw-2em">public class CrimeBaseHelper extends SQLiteOpenHelper对应于数据库操作中的DDL（数据定义语言），这里只用到了创建一个表，里面还定义了文件名称和版本。</p>



<pre class="wp-block-preformatted"><strong>public class </strong>CrimeBaseHelper <strong>extends </strong>SQLiteOpenHelper {<br>    <strong>private static final int </strong><strong><em>VERSION</em></strong>=1;<br>    <strong>private static </strong>String <em>DATABASE_NAME</em>=<strong>"crimeBase.db"</strong>;<br>    <strong>public </strong>CrimeBaseHelper(@Nullable Context context) {<br>        <strong>super</strong>(context,<em>DATABASE_NAME </em>, <strong>null</strong>, <strong><em>VERSION</em></strong>);<br>    }<br><br>    @Override<br>    <strong>public void </strong>onCreate(SQLiteDatabase db) {<br>        db.execSQL(<strong>"create table "</strong>+ CrimeTable.<strong><em>NAME</em></strong>+<strong>"("</strong>+<strong>"_id integer primary key autoincrement, "</strong>+CrimeTable.Cols.<strong><em>UUID</em></strong>+<strong>", "</strong>+CrimeTable.Cols.<strong><em>TITLE</em></strong>+<strong>", "</strong>+CrimeTable.Cols.<strong><em>DATE</em></strong>+<strong>", "</strong>+CrimeTable.Cols.<strong><em>SOLVED</em></strong>+<strong>")"</strong>);<br>    }<br><br>    @Override<br>    <strong>public void </strong>onUpgrade(SQLiteDatabase db, <strong>int </strong>oldVersion, <strong>int </strong>newVersion) {<br><br>    }<br>}</pre>



<p class="is-style-iw-2em">public class CrimeCursorWrapper extends CursorWrapper是一个游标封装类，public Crime getCrime()将从数据库中读到的一条记录封装变成一个crime。</p>



<pre class="wp-block-preformatted"><strong>public class </strong>CrimeCursorWrapper <strong>extends </strong>CursorWrapper {<em>
    </em><strong>public </strong>CrimeCursorWrapper(Cursor cursor) {
        <strong>super</strong>(cursor);
    }

    <strong>public </strong>Crime getCrime()
    {
        String uuidString=getString(getColumnIndex(CrimeTable.Cols.<strong><em>UUID</em></strong>));
        String title=getString(getColumnIndex(CrimeTable.Cols.<strong><em>TITLE</em></strong>));
        <strong>long </strong>date=getLong(getColumnIndex(CrimeTable.Cols.<strong><em>DATE</em></strong>));
        <strong>int </strong>isSolved=getInt(getColumnIndex(CrimeTable.Cols.<strong><em>SOLVED</em></strong>));

        Crime crime=<strong>new </strong>Crime((UUID.<em>fromString</em>(uuidString)));
        crime.setTitle(title);
        crime.setDate(<strong>new </strong>Date(date));
        crime.setSolved(isSolved!=0);

        <strong>return </strong>crime;
    }
}</pre>



<h3 class="wp-block-heading">有关连接到数据库</h3>



<p class="is-style-iw-2em">之前的所有数据都是通过CrimeLab来进行增删改查的，现在也不例外，所以需要在CrimeLab中打开一个数据库（利用CrimeBaseHelper）。</p>



<pre class="wp-block-preformatted"><strong>private </strong>Context <strong>mContext</strong>;
<strong>private </strong>SQLiteDatabase <strong>mDatabase</strong>;
...
<strong>private </strong>CrimeLab(Context context)
{
<strong>mContext</strong>=context.getApplicationContext();
<strong>mDatabase</strong>=<strong>new </strong>CrimeBaseHelper(<strong>mContext</strong>).getWritableDatabase();
}</pre>



<h3 class="wp-block-heading">有关利用数据库进行增删改查（DML）</h3>



<p class="is-style-iw-2em">负责数据库写入（增）和更新（改）操作需要用到辅助类ContentValues，它和HashMap、Bundle是一个键值存储类。只不过ContentValues只能用于处理SQLite的数据。创建ContentValues就是将传入的crime打包成键值对的形式。</p>



<pre class="wp-block-preformatted"><strong>private static </strong>ContentValues getContentValues(Crime crime)<br>{<br>    ContentValues values=<strong>new </strong>ContentValues();<br>    values.put(CrimeDbSchema.CrimeTable.Cols.<strong><em>UUID</em></strong>,crime.getId().toString());<br>    values.put(CrimeDbSchema.CrimeTable.Cols.<strong><em>TITLE</em></strong>,crime.getTitle());<br>    values.put(CrimeDbSchema.CrimeTable.Cols.<strong><em>DATE</em></strong>,crime.getDate().getTime());<br>    values.put(CrimeDbSchema.CrimeTable.Cols.<strong><em>SOLVED</em></strong>,crime.isSolved()?1:0);<br><br>    <strong>return  </strong>values;<br>}</pre>



<h4 class="wp-block-heading">增</h4>



<p class="is-style-iw-2em">改写CrimeLab.addCrime()方法：</p>



<pre class="wp-block-preformatted"><strong>public void </strong>addCrime(Crime c)
{
    ContentValues values=<em>getContentValues</em>(c);
    <strong>mDatabase</strong>.insert(CrimeDbSchema.CrimeTable.<strong><em>NAME</em></strong>,<strong>null</strong>,values);
}</pre>



<p class="is-style-iw-2em">insert第一个参数是表名，第二个是空值时的操作（没用到），第三个是插入的元组。</p>



<h4 class="wp-block-heading">改</h4>



<pre class="wp-block-preformatted"><strong>public void </strong>updateCrime(Crime crime)<br>{<br>    String uuidString =crime.getId().toString();<br>    ContentValues values=<em>getContentValues</em>(crime);<br>    <strong>mDatabase</strong>.update(CrimeDbSchema.CrimeTable.<strong><em>NAME</em></strong>,values, CrimeDbSchema.CrimeTable.Cols.<strong><em>UUID</em></strong>+<strong>"= ?"</strong>,<strong>new </strong>String[]{uuidString});<br>}</pre>



<p class="is-style-iw-2em">update第一个参数是表名，第二个参数是修改的结果，第三个是where语句；第三个where语句中的“?”在第四个参数中补充。</p>



<h4 class="wp-block-heading">删</h4>



<p class="is-style-iw-2em">见挑战练习。</p>



<h4 class="wp-block-heading">查</h4>



<p class="is-style-iw-2em">读取数据库对应select，SQLite用的是query。查询结果储存在游标cursor里面，但是我们之前为了方便已经定义了游标的封装类CrimeCursorWrapper了，所以就使用它。</p>



<pre class="wp-block-preformatted"><strong>private </strong>CrimeCursorWrapper queryCrimes (String whereClause, String[] whereArgs)
{
    Cursor cursor=<strong>mDatabase</strong>.query(
            CrimeDbSchema.CrimeTable.<strong><em>NAME</em></strong>,
            <strong>null</strong>,
            whereClause,
            whereArgs,
            <strong>null</strong>,
            <strong>null</strong>,
            <strong>null
    </strong>);
    <strong>return new </strong>CrimeCursorWrapper(cursor);
}</pre>



<p class="is-style-iw-2em">上面这一段方法实际上就是将select的范围锁定在表名为CrimeDbSchema.CrimeTable.NAME的表里而已。</p>



<p class="is-style-iw-2em">得到游标之后需要逐条读取每一条记录，这样才能获得所有结果。读取每一条记录并将其封装为Crime类的方法就是之前创建的CrimeCursorWrapper的getCrime()方法。接下来在说明如何用游标来读取数据。</p>



<h4 class="wp-block-heading">读-获取所有记录</h4>



<p class="is-style-iw-2em">读取所有记录并存在一个list中以便于像前几章一样供Fragment读取。所以需要重写CrimeLab.getCrimes()方法。</p>



<pre class="wp-block-preformatted"><strong>public </strong>List&lt;Crime&gt;getCrimes()<br>{<br>    List&lt;Crime&gt; crimes=<strong>new </strong>ArrayList&lt;&gt;();<br><br>    CrimeCursorWrapper cursor=queryCrimes(<strong>null</strong>,<strong>null</strong>);<br><br>    <strong>try<br></strong><strong>    </strong>{<br>        cursor.moveToFirst();<br>        <strong>while</strong>(!cursor.isAfterLast())<br>        {<br>            crimes.add(cursor.getCrime());<br>            cursor.moveToNext();<br>        }<br>    }<br>    <strong>finally </strong>{<br>        cursor.close();<br>    }<br><br>    <strong>return </strong>crimes;<br>}</pre>



<h4 class="wp-block-heading">读-获取某一个特定的记录</h4>



<p class="is-style-iw-2em">原先的CrimeLab.getCrime()方法是获取指定Id的记录。在前几张的挑战练习中要求改进查询方式，当时我就说效率提升不是很大，而且之后还需要不断修改，没想到用上数据库之后可以直接全删了。</p>



<pre class="wp-block-preformatted"><strong>public </strong>Crime getCrime(UUID id)
{
    CrimeCursorWrapper cursor=queryCrimes(CrimeDbSchema.CrimeTable.Cols.<strong><em>UUID</em></strong>+<strong>" =?"</strong>,<strong>new </strong>String[]{id.toString()});
<strong>try
    </strong>{
        <strong>if </strong>(cursor.getCount()==0)
        {
            <strong>return null</strong>;
        }
        cursor.moveToFirst();
        <strong>return </strong>cursor.getCrime();
    }
    <strong>finally </strong>{
        cursor.close();
    }
}</pre>



<h3 class="wp-block-heading">有关刷新模型层数据</h3>



<p class="is-style-iw-2em">到此为止，新建一个crime之后RecyclerView不会立刻显示出新建的项目，甚至是你重新打开这个软件之前都不会显示。</p>



<p class="is-style-iw-2em">造成这个问题的原因是RecyclerView的刷新所依据的Adapter.mCrimes是对创建其本身时对数据库的一次快照，保留的只是当时的数据，而我们对数据的增删改查全是直接对数据库进行操作，绕过了mCrimes。那么只需要每一次更新界面时，让mcrimes再重新获取一次数据库的快照就行了。</p>



<pre class="wp-block-preformatted"><strong>private void </strong>updateUI()<br>{<br>    CrimeLab crimeLab=CrimeLab.<em>get</em>(getActivity());<br>    List&lt;Crime&gt; crimes=crimeLab.getCrimes();<br>    <strong>if </strong>(<strong>mAdapter</strong>==<strong>null</strong>)<br>    {<br>        <strong>mAdapter</strong>=<strong>new </strong>CrimeAdapter(crimes);<br>        <strong>mCrimeRecyclerView</strong>.setAdapter(<strong>mAdapter</strong>);<br>    }<br>    <strong>else<br></strong><strong>    </strong>{<br>        <strong>mAdapter</strong>.setCrimes(crimes);<br>        <strong>mAdapter</strong>.notifyItemChanged(<strong>CrimePosition</strong>);<br>    }<br>    updateSubtitle();<br>}</pre>



<h2 class="wp-block-heading">有关挑战练习 删除Crime记录</h2>



<p class="is-style-iw-2em">这个简单，直接在CrimeLab方法中增加有关deleteCrime的方法。</p>



<pre class="wp-block-preformatted"><strong>public void </strong>deleteCrime(Crime crime)
{<em>
    </em>String uuidString =crime.getId().toString();
    <strong>mDatabase</strong>.delete(CrimeDbSchema.CrimeTable.<strong><em>NAME</em></strong>, CrimeDbSchema.CrimeTable.Cols.<strong><em>UUID</em></strong>+<strong>"= ?"</strong>,<strong>new </strong>String[]{uuidString});   
}</pre>



<p class="is-style-iw-2em">delete的第一个参数是要删除元组的表名，第二个参数是where语句，其中“?”由第三个参数补充。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cztcode.com/2020/2626/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2626</post-id>	</item>
		<item>
		<title>Android学习笔记(十三)</title>
		<link>https://www.cztcode.com/2020/2607/</link>
					<comments>https://www.cztcode.com/2020/2607/#comments</comments>
		
		<dc:creator><![CDATA[Jellow]]></dc:creator>
		<pubDate>Sat, 20 Jun 2020 08:26:46 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<guid isPermaLink="false">https://www.cztcode.com/?p=2607</guid>

					<description><![CDATA[Android权威编程指南第13章挑战练习
]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div>
<p class="is-style-iw-2em">Android权威编程指南第13章挑战练习</p>



<p class="is-style-iw-2em">注：本篇文章部分参考 <a href="https://www.cztcode.com/2020/java-android-learning-summary-17/" class="rank-math-link">@Shopkeeper</a>的文章，因为这两天比较忙，没有仔细地去查找资料，本篇文章基本操作都已注明。</p>



<h3 class="wp-block-heading">删除Crime记录</h3>



<p class="is-style-iw-2em">新创建一个操作栏，和书中前面的操作一样。</p>



<p class="is-style-iw-2em">首先创建一个menu布局</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="536" height="194" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-57.png" alt="" class="wp-image-2615" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-57.png 536w, https://www.cztcode.com/wp-content/uploads/2020/06/image-57-300x109.png 300w" sizes="(max-width: 536px) 100vw, 536px" /></figure>



<p class="is-style-iw-2em">我们想在crime页面添加删除按钮，将操作栏初始化写在crimeFragment中，为了更好的通用性，尽管只有一个选项还是写了switch。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="771" height="370" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-58.png" alt="" class="wp-image-2616" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-58.png 771w, https://www.cztcode.com/wp-content/uploads/2020/06/image-58-300x144.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-58-768x369.png 768w" sizes="(max-width: 771px) 100vw, 771px" /></figure>



<p class="is-style-iw-2em">在这里我们调用了deleteCrime方法删除，注意写的时候不要忘记处理HashMap的键值对对应就好，值为HashMap中的位置。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="338" height="123" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-59.png" alt="" class="wp-image-2617" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-59.png 338w, https://www.cztcode.com/wp-content/uploads/2020/06/image-59-300x109.png 300w" sizes="(max-width: 338px) 100vw, 338px" /></figure>



<p class="is-style-default">显示效果：</p>



<figure class="wp-block-gallery columns-1 is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex"><ul class="blocks-gallery-grid"><li class="blocks-gallery-item"><figure><img loading="lazy" decoding="async" width="360" height="235" src="https://www.cztcode.com/wp-content/uploads/2020/06/09.gif" alt="" data-id="2618" data-full-url="https://www.cztcode.com/wp-content/uploads/2020/06/09.gif" data-link="https://www.cztcode.com/?attachment_id=2618" class="wp-image-2618"/></figure></li></ul></figure>



<p class="is-style-iw-2em">这里也遇到了@shopkeeper的问题，添加多个crime后删除前一个会崩溃</p>



<p class="is-style-iw-2em">采用这里的<a href="https://www.jianshu.com/p/2eca433869e9" class="rank-math-link" target="_blank" rel="noopener">第一种方法</a>（最合适方便的方法）更改后消除bug。</p>



<h3 class="wp-block-heading">复数字符串资源</h3>



<p class="is-style-iw-2em">按照书上的方法写后发现无效。无论怎么变化显示的都是复数情况，经查找在中文模式下显示的始终是other。</p>



<p class="is-style-iw-2em">注：其实中文也用不上</p>



<h4 class="wp-block-heading">用于RecyclerView的空视图</h4>



<p class="is-style-iw-2em">自己写了一下，定义了一个新的视图文件，但发现onCreateView只能设置返回一个View，如果新建两个xml文件时无法找到按钮id（用inflater再初始化一个view能找到，但是没有返回这个view无法创建），所以就写进了一个xml布局文件，</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="800" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-60-1024x800.png" alt="" class="wp-image-2621" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-60-1024x800.png 1024w, https://www.cztcode.com/wp-content/uploads/2020/06/image-60-300x234.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-60-768x600.png 768w, https://www.cztcode.com/wp-content/uploads/2020/06/image-60.png 1113w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="is-style-iw-2em">看起来是这样的</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="482" height="347" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-61.png" alt="" class="wp-image-2622" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-61.png 482w, https://www.cztcode.com/wp-content/uploads/2020/06/image-61-300x216.png 300w" sizes="(max-width: 482px) 100vw, 482px" /></figure>



<p class="is-style-iw-2em">等一会设置视图可见性就好，我们再onResume中更改视图可见性，因为创建完成crime后会调用onResume。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="627" height="319" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-62.png" alt="" class="wp-image-2623" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-62.png 627w, https://www.cztcode.com/wp-content/uploads/2020/06/image-62-300x153.png 300w" sizes="(max-width: 627px) 100vw, 627px" /></figure>



<p class="is-style-iw-2em">实现效果：</p>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.cztcode.com/wp-content/uploads/2020/06/10.gif" alt="" class="wp-image-2624"/></figure>



<p class="is-style-iw-2em"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cztcode.com/2020/2607/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2607</post-id>	</item>
		<item>
		<title>Java安卓学习总结（十七）</title>
		<link>https://www.cztcode.com/2020/2605/</link>
					<comments>https://www.cztcode.com/2020/2605/#comments</comments>
		
		<dc:creator><![CDATA[Jellow]]></dc:creator>
		<pubDate>Fri, 19 Jun 2020 12:30:37 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<guid isPermaLink="false">https://www.cztcode.com/?p=2605</guid>

					<description><![CDATA[Java安卓学习总结（十七）第十三章 工具栏]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div>


<h2 class="wp-block-heading">第十三章 工具栏</h2>



<h3 class="wp-block-heading">有关工具栏菜单</h3>



<h4 class="wp-block-heading">定义资源</h4>



<p class="is-style-iw-2em">在XML中创建菜单资源，资源类型是Menu，且这个资源处于一个新的命名空间res\menu中。其中可以用Android Asset Studio来根据系统资源来创建自己想要的资源目标。</p>



<h4 class="wp-block-heading">创建菜单</h4>



<p class="is-style-iw-2em">要用到三个方法。</p>



<pre class="wp-block-preformatted"><strong>public void </strong>onCreateOptionsMenu(Menu menu, MenuInflater inflater)
@Override
<strong>public void </strong>onCreate(@Nullable Bundle savedInstanceState) {
...
setHasOptionsMenu(<strong>true</strong>);
}
<strong>public boolean </strong>onOptionsItemSelected(MenuItem item)</pre>



<p class="is-style-iw-2em">第一个方法是绑定视图资源；第二个是让这个Fragment接受调用菜单栏的指令；第三个是响应菜单项的选择事件。</p>



<h3 class="wp-block-heading">有关实现层级式导航</h3>



<p class="is-style-iw-2em">层级式导航就是规定一个activity有一个上级activity，只要点击工具栏左边的按钮就可以向上导航。定义上层activity是在AndroidMainfest.xml添加android:parentActivityName：</p>



<pre class="wp-block-preformatted">&lt;<strong>activity android:name=".CrimePagerActivity"
    android:parentActivityName=".CrimeListActivity"</strong>&gt;&lt;/<strong>activity</strong>&gt;</pre>



<h3 class="wp-block-heading">有关可选菜单项（次标题）</h3>



<p class="is-style-iw-2em">过程大致上和创建第一个菜单的相同。其中用到这样几个方法：</p>



<h4 class="wp-block-heading">设置次标题</h4>



<pre class="wp-block-preformatted">AppCompatActivity activity=(AppCompatActivity) getActivity();<br>activity.getSupportActionBar().setSubtitle(subtitle);</pre>



<h4 class="wp-block-heading">重建OptionsMenu</h4>



<pre class="wp-block-code"><code>invalidateOptionsMenu();</code></pre>



<p class="is-style-iw-2em">这个方法的作用是使原填充的菜单项无效，当用户再次访问菜单时，再次调用onCreateOptionsMenu(Menu menu)。此外还有一个onPrepareOptionsMenu()方法，可以看<a href="https://blog.csdn.net/runningzyx/article/details/52979859" class="rank-math-link" target="_blank" rel="noopener">这里</a>。</p>



<h4 class="wp-block-heading">更新菜单项</h4>



<p class="is-style-iw-2em">这一步是使得菜单项的名称发送改变。</p>



<pre class="wp-block-preformatted">MenuItem subtitleItem=menu.findItem(R.id.<strong><em>show_subtitle</em></strong>);<br><strong>if </strong>(<strong>mSubtitleVisible</strong>)<br>{<br>    subtitleItem.setTitle(R.string.<strong><em>hide_subtitle</em></strong>);<br>}<br><strong>else<br></strong>{<br>    subtitleItem.setTitle(R.string.<strong><em>show_subtitle</em></strong>);<br>}</pre>



<h3 class="wp-block-heading">有关挑战练习 删除crime记录</h3>



<p class="is-style-iw-2em">过程和新建一个crime差不多，要在crimeLab中新建一个delete方法。onOptionsItemSelected内对应的代码是</p>



<pre class="wp-block-preformatted"><strong>case </strong>R.id.<strong><em>delete_crime</em></strong>:

    CrimeLab crimeLab=CrimeLab.<em>get</em>(getActivity());
    crimeLab.deleteCrime(<strong>mCrime</strong>);<em>
    </em>getActivity().finish();
    <strong>return true</strong>;</pre>



<p class="is-style-iw-2em">由于之前用hashMap优化过crime的查询方式，所以crimeLab.add()和delete()方法都要对其hashMap进行即是更新，否则会导致查询不到或下标越界的错误。</p>



<p class="is-style-iw-2em">当我创建了多个crime时删除非最后一个，程序就会崩溃</p>



<pre class="wp-block-code"><code> java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{1006c628 position=1 id=-1, oldPos=1, pLpos:-1 scrap tmpDetached no parent}</code></pre>



<p class="is-style-iw-2em">原因是最后一个crime的View Holder的位置和Adapter不匹配，导致RecyclerView出现了问题。上网查询之后找到了<a href="https://www.jianshu.com/p/2eca433869e9" class="rank-math-link" target="_blank" rel="noopener">解决方法</a>。</p>



<p class="is-style-iw-2em">我用的是第一个方法，写了LinearLayoutManager的继承类。</p>



<pre class="wp-block-preformatted"><strong>class </strong>WrapContentLinearLayoutManager <strong>extends </strong>LinearLayoutManager {


    <strong>public </strong>WrapContentLinearLayoutManager(Context context) {
        <strong>super</strong>(context);
    }

    @Override
    <strong>public void </strong>onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        <strong>try </strong>{
            <strong>super</strong>.onLayoutChildren(recycler, state);
        } <strong>catch </strong>(IndexOutOfBoundsException e) {
            e.printStackTrace();
        }
    }
}
...
<strong>mCrimeRecyclerView</strong>.setLayoutManager(<strong>new </strong>WrapContentLinearLayoutManager(getActivity()));</pre>



<p class="is-style-iw-2em">很迷为什么只要继承一下之后就可以用了。</p>



<h3 class="wp-block-heading">有关挑战练习 复数字符串资源</h3>



<p class="is-style-iw-2em">这么样定义字符串：</p>



<pre class="wp-block-preformatted">&lt;<strong>plurals </strong><strong>name</strong><strong>="subtitle_plurals"</strong>&gt;<br>    &lt;<strong>item </strong><strong>quantity</strong><strong>="one"</strong>&gt;%1$d crime&lt;/<strong>item</strong>&gt;<br>    &lt;<strong>item </strong><strong>quantity</strong><strong>="zero"</strong>&gt;%1$d crime&lt;/<strong>item</strong>&gt;<br>    &lt;<strong>item </strong><strong>quantity</strong><strong>="other"</strong>&gt;%1$d crime&lt;/<strong>item</strong>&gt;<br>&lt;/<strong>plurals</strong>&gt;</pre>



<p class="is-style-iw-2em">这样调用：</p>



<pre class="wp-block-preformatted"><strong>int </strong>crimeCount=crimeLab.getCrimes().size();<br>String subtitle=getResources().getQuantityString(R.plurals.<strong><em>subtitle_plurals</em></strong>,crimeCount,crimeCount,crimeCount);</pre>



<p class="is-style-iw-2em">其中第二个参数开始每一个都对应第一种的每一行。</p>



<h3 class="wp-block-heading">有关挑战练习 用于RecyclerView的空视图</h3>



<p class="is-style-iw-2em">我总感觉我写的做法不是题目要求的，但是效果是实现了。</p>



<p class="is-style-iw-2em">在一个视图资源文件中包含recyclerView和空视图，在onCreateView中分别绑定。在空视图里的按钮设置监听器以创建新crime。</p>



<pre class="wp-block-preformatted">@Override
<strong>public </strong>View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle saveInstanceState)
{

    View view=inflater.inflate(R.layout.<strong><em>fragment_crime_list</em></strong>,container,<strong>false</strong>);

    <strong>mCrimeRecyclerView</strong>=(RecyclerView)view.findViewById(R.id.<strong><em>crime_recycler_view</em></strong>);
    <strong>mCrimeRecyclerView</strong>.setLayoutManager(<strong>new </strong>WrapContentLinearLayoutManager(getActivity()));

    <strong>mDefaultLayoutView</strong>=(LinearLayout) view.findViewById(R.id.<strong><em>default_layout_view</em></strong>);
    <strong>mDefaultButton</strong>=(Button)view.findViewById(R.id.<strong><em>default_button</em></strong>);
    <strong>mDefaultButton</strong>.setOnClickListener(<strong>new </strong>View.OnClickListener() {
        @Override
        <strong>public void </strong>onClick(View v) {
            Crime crime=<strong>new </strong>Crime();
            CrimeLab.<em>get</em>(getActivity()).addCrime(crime);
            Intent intent=CrimePagerActivity.<em>newIntent</em>(getActivity(),crime.getId());
            startActivity(intent);

        }
    });
    ...
    <strong>return </strong>view;
}</pre>



<p class="is-style-iw-2em">这两个视图只能二选一地显示，所以在onResume()中根据当前已经有的crime数量来选择使用哪一个。</p>



<pre class="wp-block-preformatted">@Override<br><strong>public void </strong>onResume() {<br>    <strong>super</strong>.onResume();<br>    <strong>if </strong>(CrimeLab.<em>get</em>(getActivity()).getCrimes().size()==0)<br>    {<br>        <strong>mCrimeRecyclerView</strong>.setVisibility(View.<strong><em>GONE</em></strong>);<br>        <strong>mDefaultLayoutView</strong>.setVisibility(View.<strong><em>VISIBLE</em></strong>);<br>    }<br>    <strong>else<br></strong><strong>    </strong>{<br>        <strong>mCrimeRecyclerView</strong>.setVisibility(View.<strong><em>VISIBLE</em></strong>);<br>        <strong>mDefaultLayoutView</strong>.setVisibility(View.<strong><em>GONE</em></strong>);<br>        updateUI();<br>    }<br><br>}</pre>



<figure class="wp-block-image size-large"><img decoding="async" src="https://www.cztcode.com/wp-content/uploads/2020/06/GIF-2020-6-19-20-27-16.gif" alt="" class="wp-image-2610" /></figure>



<p class="is-style-iw-2em">效果应该是实现了。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cztcode.com/2020/2605/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2605</post-id>	</item>
		<item>
		<title>Android学习笔记(十二)</title>
		<link>https://www.cztcode.com/2020/2596/</link>
					<comments>https://www.cztcode.com/2020/2596/#respond</comments>
		
		<dc:creator><![CDATA[Jellow]]></dc:creator>
		<pubDate>Fri, 19 Jun 2020 05:54:58 +0000</pubDate>
				<category><![CDATA[Android]]></category>
		<guid isPermaLink="false">https://www.cztcode.com/?p=2596</guid>

					<description><![CDATA[Android权威编程指南第十二章习题解答]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div>
<p class="is-style-iw-2em">Android权威编程指南第十二章习题解答</p>



<h3 class="wp-block-heading">更多对话框</h3>



<p class="is-style-iw-2em">其实这个题的写法和之前创建DatePicker是一样的，只不过换成了TimePicker。</p>



<p class="is-style-iw-2em">首先我们创建对话框的布局TimePicker</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="605" height="173" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-51.png" alt="" class="wp-image-2597" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-51.png 605w, https://www.cztcode.com/wp-content/uploads/2020/06/image-51-300x86.png 300w" sizes="(max-width: 605px) 100vw, 605px" /></figure>



<p class="is-style-iw-2em">创建TimePickerFragment，内容和DatePickerFragment近乎完全一样，不同之处在于TimePicker设置时间上有所区别（此处不详细写了，关于使用这两种对话框会用就好，需要使用时回来看代码）</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="415" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-52-1024x415.png" alt="" class="wp-image-2598" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-52-1024x415.png 1024w, https://www.cztcode.com/wp-content/uploads/2020/06/image-52-300x122.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-52-768x311.png 768w, https://www.cztcode.com/wp-content/uploads/2020/06/image-52.png 1029w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="is-style-iw-2em">在CrimeFragment的onActivityResult中，添加DateTime的获取</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="937" height="348" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-56.png" alt="" class="wp-image-2602" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-56.png 937w, https://www.cztcode.com/wp-content/uploads/2020/06/image-56-300x111.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-56-768x285.png 768w" sizes="(max-width: 937px) 100vw, 937px" /></figure>



<h3 class="wp-block-heading">实现响应式DialogFragment</h3>



<p class="is-style-iw-2em">这道题是前面Fragment和Activity间传值的实现，之前我们讲过用Activity托管Fragment和使用intent在父activity（或父fragment）与子activity间传值，使用fragment argument在 父activity与子fragment间传值。</p>



<p class="is-style-iw-2em">由于把DiaLog变成一个页面了，首先去更改布局文件，使用constraintlayout布局会很方便。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="514" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-53-1024x514.png" alt="" class="wp-image-2599" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-53-1024x514.png 1024w, https://www.cztcode.com/wp-content/uploads/2020/06/image-53-300x151.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-53-768x386.png 768w, https://www.cztcode.com/wp-content/uploads/2020/06/image-53-1536x771.png 1536w, https://www.cztcode.com/wp-content/uploads/2020/06/image-53.png 1545w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="is-style-iw-2em">在设定好Button的id后，我们先创建托管的Activity。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="290" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-54-1024x290.png" alt="" class="wp-image-2600" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-54-1024x290.png 1024w, https://www.cztcode.com/wp-content/uploads/2020/06/image-54-300x85.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-54-768x218.png 768w, https://www.cztcode.com/wp-content/uploads/2020/06/image-54.png 1027w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="is-style-iw-2em">在页里创建onCreateView，创建布局。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="484" src="https://www.cztcode.com/wp-content/uploads/2020/06/image-55-1024x484.png" alt="" class="wp-image-2601" srcset="https://www.cztcode.com/wp-content/uploads/2020/06/image-55-1024x484.png 1024w, https://www.cztcode.com/wp-content/uploads/2020/06/image-55-300x142.png 300w, https://www.cztcode.com/wp-content/uploads/2020/06/image-55-768x363.png 768w, https://www.cztcode.com/wp-content/uploads/2020/06/image-55.png 1100w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="is-style-iw-2em">实现效果：</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="415" height="692" src="https://www.cztcode.com/wp-content/uploads/2020/06/08.gif" alt="" class="wp-image-2603"/></figure>



<p class="is-style-iw-2em">其实可以看出来，无论是Activity和Fragment还是Fragment之间传递消息，都是子Fragment去调用父Activity或者父Fragment的相应方法。</p>



<ul class="wp-block-list"><li>Activity与Fragment   调用getAvtivity().setResult()</li><li>Fragment间     调用</li></ul>



<pre class="wp-block-preformatted">getTargetFragment().onActivityResult(getTargetRequestCode(), resultCode, intent);获取传递值</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cztcode.com/2020/2596/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2596</post-id>	</item>
	</channel>
</rss>
