<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://octopusy.github.io//https://octopusy.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://octopusy.github.io//https://octopusy.github.io/" rel="alternate" type="text/html" /><updated>2026-06-06T14:09:30+00:00</updated><id>https://octopusy.github.io//https://octopusy.github.io/feed.xml</id><title type="html">八爪章鱼</title><subtitle>专注于Android软件开发的码农,带你深入分析Android系统.目前的专题有Android编译系统,Binder机制，单元测试，深入理解Activity启动流程。
</subtitle><author><name>Richard</name></author><entry><title type="html">第三方支付客户端交易流程</title><link href="https://octopusy.github.io//https://octopusy.github.io/2020/08/20/pay/" rel="alternate" type="text/html" title="第三方支付客户端交易流程" /><published>2020-08-20T00:00:00+00:00</published><updated>2020-08-20T00:00:00+00:00</updated><id>https://octopusy.github.io//https://octopusy.github.io/2020/08/20/pay</id><content type="html" xml:base="https://octopusy.github.io//https://octopusy.github.io/2020/08/20/pay/"><![CDATA[<h2 id="第三方支付客户端交易流程">第三方支付客户端交易流程</h2>

<p>目前市面上有各种形形的刷卡App，以及MPOS刷卡机，为了探秘其中的技术实现流程，我们接下来将以一款普通的蓝牙收款pos结合Android App客户端来给大家讲讲它们的工作流程是怎样的，以及支付的完整链路又是怎样的。</p>

<p><img src="https://upload-images.jianshu.io/upload_images/4677526-e988e7e631845709.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="mpos交易支付流程图.png" /></p>

<p>密钥体系：</p>

<p>一般pos等安全硬件上，在出厂的时候，每台pos终端会预装一个离散的传输密钥，该密钥作用主要用于解密请求后端返回的加密密钥，密钥主要存放于pos安全存储区和后台加密机中，Android客户端仅做透传作用。</p>

<p>客户端App上一般会设置密钥管理模块，其中包括“主密钥更新”和工作密钥更新两个模块</p>

<p>主密钥：主要用于解开工作密钥，充当工作密钥加解密的key</p>

<p>工作密钥： 工作密钥大致可分为三组，TDK密钥，TPK密钥和TAK密钥</p>

<p>TDK密钥：主要用于磁道数据的加解密</p>

<p>TPK密钥：主要用于Pin密码数据的加解密</p>

<p>TAK密钥：主要用于mac数据计算</p>

<p>加密安全方案：</p>

<p>磁道加密算法</p>

<p>D.1. 银联磁道加密算法</p>

<p>D.1.1. 数据源构成</p>

<p>D.1.1.1. 二磁道数据源</p>

<p>二磁道数据（35域）从结束标志“？”向左第2个字节开始，再向左取8个字节作为参与加密的二磁道中发卡方信息，记为TDB2。</p>

<p>D.1.1.2. 三磁道数据源</p>

<p>类似二磁道数据源构造方法，三磁道数据（36域，如果存在）磁道信息块构造方法如下：</p>

<p>三磁道数据（36域）从结束标志“？”向左第2个字节开始，再向左取8个字节作为参与加密的三磁道中发卡方信息（若不足右补足F），记为TDB3。</p>

<p>D.1.2. 加密方式</p>

<p>采用双倍长密钥TDK分别对TDB2，TDB3进行加密，将密文输出后按照对应位置替换原先的明文数据。</p>

<p>D.1.3. 举例</p>

<p>二磁道数据（37）：</p>

<p>1234567890123456789＝0508201781999168302</p>

<p>表示为:</p>

<p>0x12 0x34 0x56 0x78 0x90 0x12 0x34 0x56 0x78 0x9D 0x05 0x08 0x20 0x17 0x81 0x99 0x91 0x68 0x30 0x20</p>

<p>三磁道数据（104）：</p>

<p>991234567890123456789=156000000000000000000378199921600000508000000000000000000000=000000000003=0000000000</p>

<p>表示为:</p>

<p>0x99 0x12 0x34 0x56 0x78 0x90 0x12 0x34 0x56 0x78 0x9D 0x15 0x60 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x03 0x78 0x19 0x99 0x21 0x60 0x00 0x00 0x50 0x80 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xD0 0x00 0x00 0x00 0x00 0x00 0x3D 0x00 0x00 0x00 0x00 0x00</p>

<p>则TDB2表示为：0x08 0x20 0x17 0x81 0x99 0x91 0x68 0x30</p>

<p>则TDB3表示为：0x00 0x00 0x00 0x3D 0x00 0x00 0x00 0x00</p>

<p>采用TDK对TDB2、TDB3分别进行TDES加密，加密后的磁道信息为：</p>

<p>ENC BLOCK1 = eTDK(0x08 0x20 0x17 0x81 0x99 0x91 0x68 0x30)</p>

<p>ENC BLOCK2 = eTDK(0x00 0x00 0x00 0x3D 0x00 0x00 0x00 0x00)</p>

<p>Mac计算</p>

<p>mackey明文：</p>

<p>DF61C75DBAAEB3CEB07F85B608153286</p>

<p>macblock：</p>

<p>0200702004C120C09813166225887854241128000000000000011211110848021000060840125840376225887854241128D14021202900102560755030303031303031383130303538343034383939303030313135367F30B0B7749FCF532600000000000000000822D0D0D000233030303130303138313030353834303438393930303031</p>

<p>【过程】</p>

<p>b) 对MAB，按每8个字节做异或（不管信息中的字符格式），如果最后不满8个字节，则添加“0X00”</p>

<p>07B7E6E4DB5D35E2</p>

<p>c) 将异或运算后的最后8个字节（RESULT BLOCK）转换成16 个HEXDECIMAL：</p>

<p>RESULT BLOCK = 30374237453645344442354433354532</p>

<p>3037423745364534</p>

<p>4442354433354532</p>

<p>d) 取前8 个字节用MAK加密，取key前16位：</p>

<p>B07F85B608153286</p>

<p>ENC BLOCK1 = DES（3037423745364534）DF61C75DBAAEB3CE = 73D3AD4C8C2A060B</p>

<p>e) 将加密后的结果与后16个字节异或：</p>

<p>73D3AD4C8C2A060B</p>

<p>4442354433354532</p>

<hr />

<p>TEMP BLOCK= 37919808BF1F4339</p>

<p>f) 用异或的结果TEMP BLOCK 再进行一次双倍长密钥算法运算。</p>

<p>ENC BLOCK2 = 3DES（37919808BF1F4339）DF61C75DBAAEB3CEB07F85B608153286 = E5B13004FCFD65A7</p>

<p>g) 将运算后的结果（ENC BLOCK2）转换成16 个HEXDECIMAL：</p>

<p>ENC BLOCK2 = E5B13004FCFD65A7 = 45354231333030344643464436354137</p>

<p>h) 取前16个字节作为MAC值。</p>

<p>取”4535423133303034”为MAC值。</p>]]></content><author><name>Richard</name></author><category term="[&quot;支付&quot;]" /><summary type="html"><![CDATA[支付]]></summary></entry><entry><title type="html">RxJava深度剖析</title><link href="https://octopusy.github.io//https://octopusy.github.io/2020/07/20/rxjava/" rel="alternate" type="text/html" title="RxJava深度剖析" /><published>2020-07-20T00:00:00+00:00</published><updated>2020-07-20T00:00:00+00:00</updated><id>https://octopusy.github.io//https://octopusy.github.io/2020/07/20/rxjava</id><content type="html" xml:base="https://octopusy.github.io//https://octopusy.github.io/2020/07/20/rxjava/"><![CDATA[<h2 id="rxjava深度剖析">RxJava深度剖析</h2>

<p><img src="https://upload-images.jianshu.io/upload_images/4677526-f52f63b538ff192d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="image.png" title="image.png" /></p>

<h1 id="背景">背景</h1>

<h2 id="响应式编程">响应式编程</h2>

<p>响应式编程简单点来说可以称为异步数据流编程，当一段逻辑关系变更之后，可以及时响应，并处理变更后的逻辑关系，我们称之为响应式编程。下面我们通过一个示例来看下。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int a=1;
int b=a+1; 
System.out.print(“b=”+b)    //  b=2 
a=10; System.out.print(“b=”+b)    //  b=2
</code></pre></div></div>
<p>我们希望达到，不管a的值如何变化，b的值永远比a大1，如果仅靠赋值更新的话，我们需要另外花精力去维护a和b的关系。而响应式编程则是希望通过某种操作符来构建这种关系：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int a=1;
int b &lt;= a+1;   // &lt;= 符号只是表示a和b之间关系的操作符 
System.out.print(“b=”+b)    //  b=2 
a=10; System.out.print(“b=”+b)    //  b=11
</code></pre></div></div>

<p>这就是是响应式的思想，它希望有某种方式能够<strong>构建关系</strong>，而不是执行某种赋值命令。</p>

<blockquote>
  <p>Rx是响应式拓展，即支持响应式编程的一种拓展,为响应式在不同语言中的实现提供指导思想</p>
</blockquote>

<h1 id="如何理解rxjava">如何理解RxJava</h1>

<p>上面我们说到Rx是响应式编程的拓展，为了理解RxJava，下面我们通过一个日常实现的场景是描述。 开发一个App，必然会涉及到App初始化，数据库初始化和登录等业务操作，登录完成之后，再跳转到新的页面。</p>

<p>场景示例图：</p>

<p><img src="https://upload-images.jianshu.io/upload_images/4677526-51ceb1601f7b04c9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="image.png" title="image.png" /></p>

<p>为了实现上面的业务逻辑，我们传统的做法可能是：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 业务1 
initDB(context) 
// 业务2 loginApp(getUserData(),new OnLoginCallback{  
     // 业务3  
     void onLoginSucc(){ 
           startActivity()  // 业务4
     }
 })
</code></pre></div></div>
<p>上面这段代码，是比较常见的实现方式，但是业务之间是严格按照顺序执行，并不能反映实际的业务之间关系，它们之间真实的业务关系应该是分别完成业务1，2，3，然后再执行业务4。至此，为了构建业务之间的真实关系，RxJava此时要派上用场了。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Observable.just(context)  
                  .map((context)-&gt;{loginApp(getUserData())})  // 执行业务3 
                  .map((context)-&gt;{initApp(context)}) // 执行业务1 
                  .map((context)-&gt;{initDB(context)}) // 执行业务2 
                  .subscribeOn(Schedulers.newThread()) 
                  .subscribe((context)-&gt;{startActivity()})  // 执行业务4
</code></pre></div></div>

<p>很多同学可能会直接用上面这段代码来实现和构建上面的业务关系，但是上面实现并不是响应式的，本质上还是在同一个线程来实现，依旧是按顺序来执行，没有真实反应业务之间的关系，那么我们应该怎么去做呢？</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 初始化App 
Observable obserInitSDK = Observable.create((context)-&gt;{   
                                          initApp(context)}).subscribeOn(Schedulers.newThread())
 // 初始化数据库 
Observable obserInitDB = Observable.create((context)-&gt;{ 
                                       initDB(context)}).subscribeOn(Schedulers.newThread())
// 完成登陆 
Observable obserLogin = Observable.create((context)-&gt;{
                                       loginApp(getUserData())})  .map((isLogin)-&gt;{returnContext()}) .subscribeOn(Schedulers.newThread()) 
// 分别完成三个业务
Observable observable = Observable.merge(obserInitSDK,obserInitDB,obserLogin) 
//分开完成三个业务之后跳转到下一个 
observable.subscribe(()-&gt;{startActivity()})
</code></pre></div></div>
<p>大家应该能很明显看到两段代码的区别，下面这段代码完全遵照了业务之间客观存在的关系，可以说代码和业务关系是完全对应的。不同的业务分别是在不同线程异步执行，当业务中存在比较耗时的操作时，响应式代码可以极大的提高程序的执行效率，降低线程阻塞。</p>

<h1 id="rxjava使用">RxJava使用</h1>

<p>RxJava使用通常分为三步：</p>

<h2 id="创建一个被观察者observable">创建一个被观察者(Observable)</h2>

<p>被观察者类似一个任务栈，在给定序列的情况下，会循环遍历栈内的任务，尽管在订阅者在订阅它们之前不会发送数据，但是每执行一个操作，被观察者都会通过 onNext（）方法通知其指定的观察者，一旦执行完或者中途异常，都会通过下面两个方法来终止。</p>

<ul>
  <li>onComplete()：执行完成时调用</li>
  <li>onError()： 抛出异常时调用
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>private Observable&lt;String&gt; observable = Observable.create(new ObservableOnSubscribe&lt;String&gt;() { 
@Override public void subscribe(ObservableEmitter&lt;String&gt; emitter) throws Exception { 
                          Log.e(TAG,"subscribe()"); emitter.onNext("next()--&gt;1"); emitter.onNext("next()--&gt;2"); 
                          emitter.onComplete(); 
                  }
  });
</code></pre></div>    </div>
  </li>
</ul>

<h2 id="创建一个观察者observer">创建一个观察者（Observer）</h2>

<p>我们可以简单的将观察者理解成对被观察者动作反应的角色，当观察者订阅了一个事件之后，被观察者发送或者传输数据时，观察者都会对齐做出反应。</p>

<p>比如上面被观察者在发送了两次onNext()事件后，通过下面的执行结果，我们可以看到观察者中的onNext()分别回应了被观察者发出的两次onNext()操作。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Observer&lt;String&gt; observer = new Observer&lt;String&gt;() { 
 @Override public void onSubscribe(Disposable d) {
        Log.i(TAG, "onSubscribe"); 
  }  
 @Override public void onNext(String s) { 
        Log.i(TAG, "onNext:" + s);
 }   
@Override public void onError(Throwable e) { 
        Log.i(TAG, "onError"); 
}  
@Override public void onComplete() { 
        Log.i(TAG, "onComplete");
 }
 };
</code></pre></div></div>

<h2 id="创建订阅关系">创建订阅关系</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>observable.subscribe(observer);
</code></pre></div></div>
<h2 id="执行结果">执行结果</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2020-09-04 16:33:46.502 30049-30049/com.qtopay.rxjava20 I/MainActivity-Observer: onSubscribe
2020-09-04 16:33:46.502 30049-30049/com.qtopay.rxjava20 E/MainActivity-Observer: subscribe()
2020-09-04 16:33:46.502 30049-30049/com.qtopay.rxjava20 I/MainActivity-Observer: onNext:next()--&gt;1 
2020-09-04 16:33:46.502 30049-30049/com.qtopay.rxjava20 I/MainActivity-Observer: onNext:next()--&gt;2 
2020-09-04 16:33:46.502 30049-30049/com.qtopay.rxjava20 I/MainActivity-Observer: onComplete
</code></pre></div></div>
<h1 id="操作符简介">操作符简介</h1>

<h2 id="just">just()</h2>

<h3 id="observablejust">Observable.just（）</h3>

<p>你可以使用.just（）运算符将任何对象转换为Observable。结果Observable将发出原始对象并完成。</p>

<p>例如，这里我们创建一个Observable，它将向其所有Observers发出一个字符串：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Observable &lt;String&gt; observable = Observable.just（“Hello World！”）;
</code></pre></div></div>

<h2 id="fron">fron（）</h2>

<h3 id="observablefrom">Observable.from（）</h3>

<p>.from（）运算符允许你将对象集合转换为可观察的流。你可以使用Observable.fromArray将数组转换为Observable，使用Observable.fromCallable将Callable转换为Observable，使用Observable.fromIterable将Iterable转换为Observable。</p>

<h2 id="range">range()</h2>

<h3 id="observablerange">Observable.range（）</h3>

<p>你可以使用.range（）运算符发出一系列连续的整数。你提供的第一个整数是初始值，第二个是你要发出的整数。例如：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Observable &lt;Integer&gt; observable = Observable.range（0,5）;
</code></pre></div></div>

<h2 id="interval">interval()</h2>

<h3 id="observableinterval">Observable.interval（）</h3>

<p>此运算符创建一个Observable，它发出无限的上升整数序列，每个发射由你选择的时间间隔分隔。例如：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Observable &lt;Long&gt; observable = Observable.interval（1，TimeUnit.SECONDS）
</code></pre></div></div>

<h2 id="empty">empty()</h2>

<h3 id="observableempty">Observable.empty（）</h3>

<p>empty（）运算符创建一个Observable，它不会发出任何项目但会正常终止，这在你需要为测试目的快速创建Observable时非常有用。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Observable &lt;String&gt; observable = Observable.empty();
</code></pre></div></div>

<h1 id="附录">附录</h1>

<h2 id="日常开发使用场景">日常开发使用场景</h2>

<h3 id="1-防止按钮重复点击">1. 防止按钮重复点击；</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;RxView.clicks(mClearContent).debounce(300, TimeUnit.MILLISECONDS)
</code></pre></div></div>
<h3 id="2-edittext-添加-addtextchangedlistener">2. EditText 添加 addTextChangedListener</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RxTextView.textChanges(mUserNameEt)
</code></pre></div></div>
<h3 id="3-延迟执行">3. 延迟执行</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Observable.timer(3, TimeUnit.SECONDS)
</code></pre></div></div>
<h3 id="4-不断轮询">4. 不断轮询</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Observable.interval(3, 3, TimeUnit.SECONDS)
</code></pre></div></div>
<h3 id="5-减少频繁调用">5. 减少频繁调用</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RxTextView.textChanges(mUserNameEt)
</code></pre></div></div>
<h3 id="6-rxpermission请求权限">6. RxPermission请求权限</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rxPermissions.request(Manifest.permission.CAMERA)
</code></pre></div></div>]]></content><author><name>Richard</name></author><category term="[&quot;Android&quot;]" /><summary type="html"><![CDATA[android]]></summary></entry><entry><title type="html">探秘MPAndroid</title><link href="https://octopusy.github.io//https://octopusy.github.io/2020/07/17/barchart/" rel="alternate" type="text/html" title="探秘MPAndroid" /><published>2020-07-17T00:00:00+00:00</published><updated>2020-07-17T00:00:00+00:00</updated><id>https://octopusy.github.io//https://octopusy.github.io/2020/07/17/barchart</id><content type="html" xml:base="https://octopusy.github.io//https://octopusy.github.io/2020/07/17/barchart/"><![CDATA[<h2 id="探秘mpandroid">探秘MPAndroid</h2>

<h3 id="前言">前言</h3>

<p>人们对图像接收的敏感度远超对文字的敏感度，特别是针对部分数据统计和展示的时候，有些App会通过酷炫的图表来展示统计的数据，让用户可以方便快捷的读懂数据，今天我们就来讲讲如何使用<strong>MPAndroidChart</strong>开源库来实现我们需要的图表效果,本篇文章以柱状图为例,其它样式可参考实现。</p>

<h3 id="mpandroid-chart集成效果">MPAndroid Chart集成效果</h3>

<p>在开始集成之前，我们先来看下集成后的效果图。</p>

<p><img src="/assets/images/Android/mpAndroidChart/barchart/mpAndroid-LineChart.png" alt="背景图" /></p>

<h3 id="mpandroid-chart接入">MPAndroid Chart接入</h3>

<p><strong>定义组装Chart数据</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>private BarChart chart1;
private BarChartView mBarCharts;
private CountMarkerView countMarkerView;

private ArrayList&lt;String&gt; benfitXListData;  // X轴数据
private ArrayList&lt;BarEntry&gt; benfitYListData;  // Y轴数据

// 请求月份
private String requestMonth = "";
// 统计月份数据
private List&lt;BenfitMonthCountModel&gt; benfitMonthCountModelList = null;
private Context mContext = MainActivity.this;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    chart1 = findViewById(R.id.chart1);
    mBarCharts = new BarChartView();
    countMarkerView = new CountMarkerView(mContext,onSelectMarkerViewListener);
    chart1.setMarker(countMarkerView);
    requestWalletCountList(requestMonth,"01");
}

private OnSelectMarkerViewListener onSelectMarkerViewListener = new OnSelectMarkerViewListener() {
    @Override
    public void onSelectMarkViewPosition(int position) {
        try {
            Toast.makeText(mContext,"position:" + position,Toast.LENGTH_LONG).show();
            // todo 可以处理点击柱状图点击相关的事件
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
};


/**
 * 请求账单统计数据
 */
private void requestWalletCountList(String month,String changeType){
    try {
        benfitMonthCountModelList = DataSourceHelper.getBenfitCountList();
        if(null != benfitMonthCountModelList &amp; benfitMonthCountModelList.size() &gt; 0) {
            // 解析chartBarView
            benfitXListData = new ArrayList&lt;&gt;();
            benfitYListData = new ArrayList&lt;BarEntry&gt;();

            // 解析查询到的数据
            for(int i= 0;i&lt; benfitMonthCountModelList.size();i++) {
                String transDate = benfitMonthCountModelList.get(i).getMONTH();
                if(requestMonth.contentEquals(transDate)) {
                    Toast.makeText(mContext,"position:" + i,Toast.LENGTH_LONG).show();
                }
                transDate = PublicMethodUtils.getFormatMonthDateTime(transDate);
                benfitXListData.add(transDate);

                float transAmt = TextUtils.isEmpty(benfitMonthCountModelList.get(i).getSumAmount())?0:
                        Float.parseFloat(PublicMethodUtils.parseFenToYuan(benfitMonthCountModelList.get(i).getSumAmount()));

                benfitYListData.add(new BarEntry(i,transAmt));
            }

            countMarkerView.setxListData(benfitXListData);
            mBarCharts.barChatHiddenSetting(mContext,chart1,benfitXListData,true,"");
            mBarCharts.setBarChartShowData(mContext,benfitYListData,chart1);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
</code></pre></div></div>
<p><strong>布局文件 activity_main.xml</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center"
    android:gravity="center"
    android:orientation="vertical"&gt;

    &lt;com.github.mikephil.charting.charts.BarChart
        android:id="@+id/chart1"
        android:layout_width="match_parent"
        android:layout_gravity="center"
        android:layout_height="500dp" /&gt;

&lt;/LinearLayout&gt;
</code></pre></div></div>

<p>解释一下上面的代码,由于MPAndroid库接收的Y轴数据类型为<strong>Float</strong>类型，我们在组装给barChart赋值之前，需要转换和组装下数据，然后再赋值进去。</p>

<p><strong>BarChart属性设置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/**
 * 图表设置
 *
 * @param chart
 * @param xListData
 * @param dottNum
 */
public void barChatHiddenSetting(Context mContext, BarChart chart, ArrayList&lt;String&gt; xListData, boolean dottNum, String yearData) {
    chart.setDrawBarShadow(false); // 设置是否绘制阴影
    chart.setDrawValueAboveBar(true); // 设置是否在柱状图上绘制值
    chart.getDescription().setEnabled(false); // 设置是否显示图表描述
    // if more than 60 entries are displayed in the chart, no values will be
    // drawn
    chart.setMaxVisibleValueCount(12); // 设置最大可见数量
    // scaling can now only be done on x- and y-axis separately
    chart.setPinchZoom(true); // 如果设置为true，没缩放功能。如果false，x轴和y轴可分别放大
    chart.setDrawGridBackground(false); // 设置是否绘制网格背景
    // chart.setDrawYLabels(false); // 设置是否绘制Y轴坐标

    chart.setDragEnabled(true); // 设置是否可以拖拽
    chart.setScaleEnabled(false); // 设置图表是否可以放大缩小

    XAxis xAxis = chart.getXAxis();
    xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);  
    xAxis.setDrawGridLines(false);// 设置是否绘制X轴网格线
    xAxis.setGranularity(1f); // 设置X轴刻度，没有设置会根据X轴数据量等比显示
    xAxis.setLabelCount(xListData.size()); // 设置X轴坐标数量
    xAxis.setValueFormatter(new MyFormatXAxisValueFormatter(xListData)); // 自定义显示X轴数据

    ValueFormatter custom = new MyYAxisValueFormatter(dottNum);
    YAxis leftAxis = chart.getAxisLeft();
    leftAxis.setLabelCount(12, false);
    leftAxis.setValueFormatter(custom);
    leftAxis.setPosition(YAxis.YAxisLabelPosition.OUTSIDE_CHART);
    leftAxis.setStartAtZero(true);
    leftAxis.setGranularityEnabled(true);
    leftAxis.setGranularity(1.0f);
    leftAxis.setEnabled(true);
    leftAxis.setDrawZeroLine(true);
    leftAxis.setDrawLabels(false);
    leftAxis.setDrawAxisLine(false);
    leftAxis.setDrawTopYLabelEntry(false);
    leftAxis.setDrawGridLines(false);
    leftAxis.setSpaceTop(50);

    YAxis rightAxis = chart.getAxisRight();
    rightAxis.setEnabled(false);
    Legend mLegend = chart.getLegend();
    mLegend.setEnabled(true);
    mLegend.setHorizontalAlignment(Legend.LegendHorizontalAlignment.LEFT);
    mLegend.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM);
    mLegend.setOrientation(Legend.LegendOrientation.HORIZONTAL);
    mLegend.setDrawInside(false);
    mLegend.setFormToTextSpace(-16f);
    mLegend.setForm(Legend.LegendForm.NONE);
    mLegend.setExtra(new LegendEntry[]{
            new LegendEntry(
                    "2020",
                    Legend.LegendForm.NONE,
                    20f,
                    10f,
                    null,
                    mContext.getResources().getColor(R.color.colorPrimary)),
    });

    Matrix m = new Matrix();
    m.postScale(scaleWalletNum(xListData.size()), 1f);//两个参数分别是x,y轴的缩放比例。例如：将x轴的数据放大为之前的1.5倍
    chart.getViewPortHandler().refresh(m, chart, false);//将图表动画显示之前进行缩放
}

//30个横坐标时，缩放4f是正好的。
private float scaleWalletPercent = 4f/30f;

private float scaleWalletNum(int xCount){
    return xCount * scaleWalletPercent;
}

public void setBarChartShowData(Context mContext, ArrayList&lt;BarEntry&gt; tradeYListData, BarChart chart) {
    BarDataSet barDataSet = new BarDataSet(tradeYListData, "");
    List&lt;Fill&gt; gradientFills = new ArrayList&lt;&gt;();
    gradientFills.add(new Fill(mContext.getResources().getColor(R.color.colorPrimary), mContext.getResources().getColor(R.color.colorPrimary)));
    barDataSet.setFills(gradientFills);
    ArrayList&lt;IBarDataSet&gt; dataSets = new ArrayList&lt;&gt;();
    dataSets.add(barDataSet);

    barDataSet.setDrawValues(true);
    barDataSet.setValueFormatter(new MyDataAxisValueFormatter(true));
    BarData data = new BarData(dataSets);
    data.setValueTextSize(10f);
    // data.setBarWidth(0.9f);
    data.setBarWidth(scaleNum(tradeYListData.size()));

    chart.setData(data);
    chart.invalidate();
}

//30个横坐标时,缩放4f是正好的。
private float scalePercent = 4f/30f;  // 默认值

private float scaleNum(int xCount){
    float barWidth = 0.9f;
    if(xCount &lt;= 4) {
        barWidth = xCount * scalePercent;
    }
    if(barWidth &gt; 0.9f) {
        barWidth = 0.9f;
    }
    return barWidth;
}
</code></pre></div></div>

<h3 id="mpandroid-chart属性介绍">MPAndroid Chart属性介绍</h3>

<h4 id="图表属性">图表属性</h4>
<p><strong>1、刷新</strong></p>

<p>   invalidate()：在chart中调用会使其刷新重绘。</p>

<p>   notifyDataChanged()：让chart知道它依赖的基础数据已经改变，并执行所有必要的重新计算（比如偏移量，lenged，最大值，最小值…）。在动态添加数据时需要用到。</p>

<p><strong>2、打印日志</strong></p>

<p>   setLogEnable(boolean enabled)：设置为true将激活chart的logcat输出。但这不利于性能，如果不是必要的，应保持禁用。</p>

<p><strong>3、chart属性</strong></p>

<p>   setBackgroundColor(int color)：设置背景颜色，将覆盖整个图表视图。此外，背景颜色可以在布局文件.xml中进行设置。</p>

<p>   setDescription(Description desc)：设置图表的描述文字，会显示在图表的右下角。</p>

<p>   setDescriptionColor(int color)：设置描述文字的颜色。</p>

<p>   setDescriptionPosition(float x，floaty)：自定义描述文字在屏幕上的位置(单位是像素)。</p>

<p>   setDescriptionTypeface(Typeface t)：设置描述文字的字体。</p>

<p>   setDescriptionTextSize(float size)：设置以像素为单位的描述文字，最小6f，最大16f。</p>

<p>   setNoDataTextDescription(String desc)：设置当chart为空时显示的描述文字。</p>

<p>   setDrawGridBackground(boolean enabled)：如果启用，chart绘图区后面的背景矩形将绘制。</p>

<p>   setGridBackgroundColor(int color)：设置网格背景应与绘制的颜色。</p>

<p>   setDrawBorder(boolean enabled)：启用/禁用绘制图表边框（chart周围的线）。</p>

<p>   setBorderColor（int color）：设置chart边框线的颜色。</p>

<p>   setBorderWidth（float width）：设置chart边界线的宽度，单位dp。</p>

<p>   setMaxVisibleValueCount（int count）：设置最大可见绘制的chartcount的数量。只在setDrawValues（）设置为true时有效。</p>

<p><strong>4、启用/禁用  手势交互</strong></p>

<p>   setTouchEnabled(boolean enabled)：启用/禁用与图表的所有可能的触摸交互。</p>

<p>   setDragEnabled(boolean enabled)：启用/禁用拖动（平移）图表。</p>

<p>   setScaleEnabled（boolean enabled）：启用/禁用缩放图表上的两个轴。</p>

<p>   setScaleXEnabled(boolean enabled)：启用/禁用缩放在X轴上。</p>

<p>   setScaleYEnabled(boolean enabled)：启用/禁用缩放在Y轴上。</p>

<p>   setPinchZoom（boolena enabled）：如果设置为true，没缩放功能。如果false，x轴和y轴可分别放大。</p>

<p>   setDoubleTapToZoomEnabled（booleanenabled）：设置为false以禁止通过在其上双击缩放图表。</p>

<p>   setHighlightPerDragEnabled（booleanenabled）：设置为true，允许每个图表表面拖过，当它完全缩小突出。默认值：true</p>

<p>   setHighlightPerTapEnabled(boolean enabled)：设置为false，以防止值由敲击姿态被突出显示。值仍然可以通过拖动或编程方式突出显示。默认值：true。</p>

<p><strong>5、图表的抛掷/减速</strong></p>

<p>   setDragDecelerationEnabled(boolean enabled)：如果设置为true，手指滑动抛掷图表后继续减速滚动。默认值：true。</p>

<p>   setDragDecelerationFrictionCoef（floatcoef）：减速的摩擦系数在[0；1]区间，数值越高表示速度回缓慢下降，例如，如果将其设置为0，将立即停止。1是一个无效的值，会自动转换至0.9999。</p>

<p><strong>6、高亮</strong></p>

<p>    highlightValues（Highlight[] highs）：高亮显示值，高亮显示的点击的位置在数据集中的值。设置null或空数组则撤销所有高亮。</p>

<p>    highlightValue（int xIndex，intdataSetIndex）：高亮给定xIndex在数据集的值。设置xIndex或dataSetIndex为-1撤销所有高亮。</p>

<p>    getHighlightd（）：返回一个highlight[]其中包含所有高亮对象的信息，xIndex和dataSetIndex。以java编程方式使得值高亮不会回调onChartValueSelectedListener。</p>

<p><strong>7、选择回调</strong></p>

<p>    MPAndroidChart提供了许多用于交互回调的方法，其中OnChartValueSelectedListener在点击高亮时回调。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>publicinterface OnChartValueSelectedListener{
   /**   
    * 当点击图表里面的值
    * @param e 选择的数据集
    * @param dataSetIndex  数据集的索引
    * @param h  相应的突出对象
    */  
    public voidonValueSelected(Entry e, int dataSetIndex, Highlight h);

   /**  
    * 当没有选择时
    */
    public voidonNothingSelected();
}
</code></pre></div></div>
<p>让你的类实现该接口并设置对chart进行监听，即可接收回调。</p>

<p><strong>8、图表自属方法</strong></p>

<p><strong>雷达图（RadarChart）</strong></p>

<p>setWebLineWidth(float width)             设置向外放射线条宽度</p>

<p>setWebLineWidthInner(floatwidth)         设置内部环线宽度</p>

<p>setSkipWebLineCount(intcount)            设置隐藏count条放射线条</p>

<p>setWebAlpha(int alpha)          设置背景透明度</p>

<p>setWebColor(int color)          设置向外放射线颜色</p>

<p>setWebColorInner(int color)     设置内部环线颜色</p>

<p>setDrawWeb(boolean enabled)     设置是否显示雷达图
 </p>

<p><strong>柱形图（BarChart）</strong></p>

<p>setDrawValueAboveBar(boolean enabled)  设置数字显示在柱形条上部或下部</p>

<p>setDrawBarShadow(boolean enabled)  设置是否显示全部柱形条，不填充部分显示灰色</p>

<p>setHighlightFullBarEnabled(boolean enabled)  设置是否高亮显示</p>

<p>setFitBars(boolean enabled)  设置X轴范围两侧柱形条是否显示一半
 </p>

<p><strong>饼图（PieChart）</strong></p>

<p>setUsePercentValues(booleanenabled)  设置图表内值显示为原始值或百分之</p>

<p>setHoleColor(int color)     设置中心圈背景颜色</p>

<p>setDrawSlicesUnderHole(boolean enable)   设置在中心圈下面是否显示切片</p>

<p>setDrawHoleEnabled(boolean enabled)   设置是否显示中心圈部分</p>

<p>setCenterText(CharSequence text)    设置中心圈文字</p>

<p>setDrawCenterText(boolean enabled)   设置是否显示中心圈文字</p>

<p>setHoleRadius(final float percent)   设置中心圈半径占整个饼状图半径的百分比，默认50%</p>

<p>setTransparentCircleColor(int color)    设置透明圈的颜色</p>

<p>setTransparentCircleRadius(final float percent)设置透明圈半径占整个饼状图半径的百分比，默认55%</p>

<p>setTransparentCircleAlpha(int alpha)    设置透明圈的透明度</p>

<p>setMaxAngle(float maxangle)       设置饼图的最大角度，90&lt;= maxangle&lt;=360
 </p>

<h4 id="坐标轴">坐标轴</h4>

<p><strong>1、Document</strong></p>

<p>     AxisBase是XAxis和YAxis的父类</p>

<p>     AxisBase 所有标签的基类</p>

<p>     XAxis    X轴标签设置。只是用setter方法来修改它，不要直接访问公共变量。（对于RadarChart蜘蛛网状图不是所有的Xlabls都适用。）</p>

<p>     YAxis    Y轴标签设置和它的条目。只使用setter方法来修改它，不要直接访问公共变量。（ 对于RadarChart蜘蛛网状图不是所有的Ylabls都适用。 ）在为chart设置data之前，影响轴的值范围的Customizations需要先被应用。</p>

<p><strong>2、概述</strong></p>

<p>     下面提及的方法可以适用于两个轴。</p>

<p>     “轴”类允许特定的Style，由以下components/parts组成（可以包含）：</p>

<p>     轴的标签（y轴垂直绘制或X轴水平取向），contain轴的描述值。</p>

<p>     所谓axis-line被直接绘制在标签旁且平行。</p>

<p>     grid-lines在水平方向，且源自每一个轴标签。</p>

<p>     LimitLines允许呈现的特别信息，如边界或限制。</p>

<p><strong>3、控制轴的绘制。</strong></p>

<p>    setEnable（boolean enabled）：设置轴启用或禁用。如果false，该轴的任何部分都不会被绘制（不绘制坐标轴/便签等）。</p>

<p>    setDrawGridLines(boolean enabled)：设置为true，则绘制网格线。</p>

<p>    setDrawAxisLines(boolean enabled)：设置为true，则绘制该行旁边的轴线（axis-line）。</p>

<p>    setDrawLables(boolean enabled)：设置为true，则绘制轴的标签。</p>

<p><strong>4、修改轴</strong></p>

<p>    setTextColor(int color)：设置轴标签的颜色。</p>

<p>    setTextSize(float size)：设置轴标签的文字大小。</p>

<p>    setTypeface（Typeface tf）：设置周标倩的Typeface。</p>

<p>    setGridColor（int color）：设置该轴的网格线颜色。</p>

<p>    setGridLineWidth（float width）：设置该轴网格线的宽度。</p>

<p>    setAxisLineColor（int color）：设置轴线的轴的颜色。</p>

<p>    setAxisLineWidth（float width）：设置该轴轴行的宽度。</p>

<p>    enableGridDashedLine（float lineLength，float spaceLength，float phase）：启用网格线的虚线模式中得出，比如像这样“- - - -”。</p>

<p>    <strong>lineLength</strong>控制虚线段的长度</p>

<p>    <strong>spaceLength</strong>控制线之间的空间</p>

<p>    <strong>phase</strong>控制线的起始点</p>

<p><strong>5、限制线</strong></p>

<p>   两个轴支持LimitLines来呈现特定信息，如边界或限制线。LimitLines加入到YAxis在水平方向上绘制，添加到XAxis在垂直方向绘制。如何通过给定的轴添加和删除LimitLines：</p>

<p>   addLimitLines（LimitLine l）：给该轴添加一个新的LimitLine。</p>

<p>   removeLimitLine（LimitLine l）：从该轴删除指定的LimitLine。</p>

<p>   removeAllLimitLines()：删除所有的LimitLine。</p>

<p>   getLimitLines()：获得所有的 LimitLine</p>

<p>   setDrawLimitLineBehindData（booleanenable）：控制LimitLines与actual data之间的z-order。如果设置为true，LimitLines绘制在actualdata的后面，否则在其前面。默认值：false。</p>

<p><strong>6、概述</strong></p>

<p>     XAxis、YAxis类是AxisBase的一个子类。</p>

<p>     XAxis类是所有与水平轴相关的“数据和信息容器”。每个LineChart，BarChart，ScateerChart，CandleStickChart和RadarChart都有一个XAxis对象。XAxis对象展示了以ArrayList<String>或String[]（“xVals”）形式递交给ChartData对象的数据。得到XAxis的实例：XAxisxAxis = chart.getXAxis()；</String></p>

<p>     YAxis 类是一切与垂直轴相关的数据和信息的容器。 每个 LineChart,BarChart , ScateerChart or CandleStickChart 都有 left 和 right 的 YAxis 的对象，分别在左右两边。 但是 RadarChart 只有一个 YAxis 。 缺省情况下，图表的两个轴都被启用，并且将被绘制。 得到XAxis的实例：YAxisyAxis = chart.getYAxis()；</p>

<p><strong>7、属性：</strong></p>

<p>  isDrawAxisLineEnabled()：返回坐标轴是否能被绘制（return true/fasle）</p>

<p>  setCenterAxisLabels(boolean enabled)：设置标签是否居中</p>

<p>  isCenterAxisLabelsEnabled()：返回坐标轴标签是否居中</p>

<p>  setDrawLabels(boolean enabled)：设置是否可以绘制文本</p>

<p>  isDrawLabelsEnabled()：返回坐标轴是否可以绘制文本</p>

<p>  setLabelCount(int count)：设置坐标轴的标签数量，当count&gt;25时，count=25；当count&lt;2时，count=2</p>

<p>  setLabelCount(int count, boolean force)： 设置坐标轴的标签数量， 这个数字是不固定 if(force ==false)，只能是近似的。如 果if(force ==true)，则确切绘制指定数量的标签，但这样可能导致轴线分布不均匀。</p>

<p>  isForceLabelsEnabled()：返回是否强加标签。默认：false</p>

<p>  getLabelCount()：返回标签数量</p>

<p>  setGranularityEnabled(boolean enabled)：设置是否可以设置间隔</p>

<p>  isGranularityEnabled()：返回是否可以设置间隔</p>

<p>  getGranularity()：返回坐标轴间隔大小</p>

<p>  setGranularity (float granularity)：设置坐标轴间隔大小 </p>

<p>  getLongestLabel()：返回坐标轴最长的文本，String类型</p>

<p>  getFormattedLabel(int index)：返回坐标轴的格式化文本，String类型</p>

<p>  setValueFormatter(IAxisValueFormatter f)：设置坐标轴文本的格式</p>

<p>  getAxisMaximum()：返回坐标轴的最大值，float类型</p>

<p>  getAxisMinimum()：返回坐标轴的最小值，float类型</p>

<p>  setAxisMinimum(float min)：设置坐标轴的最小值</p>

<p>  setAxisMaximum(float max)：设置坐标轴的最大值</p>

<p>  calculate(float dataMin, float dataMax)：计算坐标轴的最大值和最小值的差值</p>

<p>  setSpaceMin(float mSpaceMin)：设置坐标轴额外的最小的空间</p>

<p>  setSpaceMax(floatmSpaceMax)： 设置坐标轴额外的最大的空间</p>

<p>   setAvoidFirstLastClipping(booleanenabled):  设置X轴第一个和最后一个标签超出屏幕</p>

<p>   setLabelRotationAngle(float angle)：  设置X轴标签文字的方向</p>

<p>   setPosition(XAxisPosition pos)：  设置X轴标签的位置</p>

<p>   setZeroLineWidth(float width)：  设置Y轴第一条线的宽度</p>

<p>   setZeroLineColor(int color)：  设置Y轴第一条线的颜色</p>

<p>   setDrawZeroLine(boolean mDrawZeroLine)：  设置Y是否显示第一条线</p>

<p>   setSpaceBottom(float percent)：  设置底部距离</p>

<p>   setSpaceTop(float percent)：  设置顶部距离</p>

<p>   setStartAtZero(boolean startAtZero)：  设置Y轴是否从0开始</p>

<p>   setInverted(boolean enabled)：  设置Y轴是否翻转</p>

<p>   setDrawTopYLabelEntry(boolean enabled)：  设置是否显示顶部标签</p>

<p>   setPosition(YAxisLabelPosition pos)：  设置Y轴标签位置</p>

<h4 id="数据dataset">数据（DataSet）</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>DataSet 类是所有数据集类的基类，是 Chart 中一组或一类的 Entry 的集合。 它被设计成 Chart 内部逻辑上分离的不同值组（例如， LineChart 中特定行的值或 BarChart 中特定bar组的值）。
</code></pre></div></div>

<p><strong>属性</strong></p>

<p>   setMode(LineDataSet.Mode mode):设置模式</p>

<p>   CUBIC_BEZIER   立方曲线</p>

<p>   LINEAR   直线</p>

<p>   STEPPED 阶梯</p>

<p>   HORIZONTAL_BEZIER  水平曲线</p>

<p>   setCubicIntensity(float intensity)：设置曲线的弯曲程度</p>

<p>   getColors()：返回颜色（LineChart：折线；BarChart：柱图等），List<Integer>类型</Integer></p>

<p>   getValueColors()：返回文本颜色， List<Integer>类型</Integer></p>

<p>   getColor()：返回索引为0的颜色，int类型</p>

<p>   getColor(int index)：返回索引为index的颜色，int类型</p>

<p>   setColors(List<Integer> colors)/setColors(int...colors)/setColors(int[] colors, Context c)：设置颜色</Integer></p>

<p>   setLabel(String label)：设置文本</p>

<p>   getLabel()：获得文本，String类型</p>

<p>   setHighlightEnabled(boolean enabled) : 设置为true，允许通过点击高亮突出 ChartData 对象和其 DataSets </p>

<p>   setValueFormatter(IValueFormatter f)：设置文本格式</p>

<p>   setValueTextColor(intcolor)/setValueTextColors(List<Integer> colors)：设置文本颜色</Integer></p>

<p>   setValueTypeface(Typeface tf)：设置文本的字体</p>

<p>   setValueTextSize(float size)：设置文本的字体大小</p>

<p>   setForm(Legend.LegendForm form)：设置形状的大小</p>

<p>   setFormLineWidth(float formLineWidth)：设置线的宽度</p>

<p>   setDrawValues(boolean enabled)：设置是否显示文本</p>

<p>   setVisible(boolean visible)：设置是否显示</p>

<p>   setDrawFilled(boolean filled)：设置是否填充</p>

<p>   setFillAlpha(intalpha)：设置填充透明度</p>

<p>   setFillColor(int color)：设置填充颜色</p>

<p>   setFillDrawable(Drawable drawable)：设置填充drawable</p>

<p>   setDrawCircleHole(boolean enabled)：设置是否实心</p>

<p>   removeFirst():移除第一个值</p>

<p>   removeEntry(int index):移除下标index的值 </p>

<p>   removeLast():移除最后一个值</p>

<p>   removeEntryByXValue(float xValue)；根据值移除，不建议使用</p>

<h4 id="legend">Legend</h4>

<p>  Legend 通常由一个标签的 形式/形状 来表示多个条目( entries )的每一个。</p>

<p>  Entries 数量自动生成的legend 取决于DataSet 的标签 不同颜色的数量（在所有 DataSet 的对象）。 Legend 的标签取决于图表中所使用的 DataSet对象。如果没有为 DataSet 对象指定标签，图表将自动生成它们。如果多个颜色用于一个 DataSet ，这些颜色分类，只通过一个标签说明。</p>

<p><strong>属性</strong></p>

<p>  setXOffset(floatxOffset)：设置在X轴方向的偏移量</p>

<p>  setYOffset(floatyOffset)：设置在Y轴方向的偏移量</p>

<p>  setTypeface(Typefacetf)： 设置文本的字体</p>

<p>  setTextSize(floatsize)：设置文本字体大小</p>

<p>  setTextColor(intcolor)：设置文本颜色</p>

<p>  setEnabled(booleanenabled)：设置是否可用（简单理解为是否显示）</p>

<p>  setEntries(List<LegendEntry> entries);           设置图例，传LegendEntry的集合</LegendEntry></p>

<p>  setExtra(LegendEntry[]entries)    设置图例，传LegendEntry数组</p>

<p>  setExtra(List<Integer>colors, List<String> labels) 设置图例，传color的集合和  LegendEntry的集合</String></Integer></p>

<p>  setExtra(int[]colors, String[] labels) 设置图例，传color的数字和LegendEntry数组</p>

<p>  setCustom(LegendEntry[] entries)      设置图例，灰色图标不可见</p>

<p>setCustom(List<LegendEntry> entries)   设置图例，灰色图标不可见</LegendEntry></p>

<p>setHorizontalAlignment(LegendHorizontalAlignment value)  设置水平对齐方式</p>

<p>setVerticalAlignment(LegendVerticalAlignment value)  设置垂直对齐方式</p>

<p>setOrientation(LegendOrientation value)   设置方向</p>

<p>setDrawInside(boolean value)    设置是否画在图表里</p>

<p>setDirection(LegendDirection pos)    设置文字的方向</p>

<p>  setForm(LegendForm shape)    设置形状</p>

<p>  setFormSize(float size)    设置形状大小</p>

<p>  setFormLineWidth(float size)    设置线条宽度（形状为线状时）</p>

<p>  setFormLineDashEffect(DashPathEffect dashPathEffect) 设置线状轨迹</p>

<p>  setXEntrySpace(float space)   设置图例水平方向的间距</p>

<p>  setYEntrySpace(float space)    设置图例垂直方向的间距</p>

<p>  setFormToTextSpace(float space)  设置图例和文字的间距</p>

<p>  setWordWrapEnabled(boolean enabled)  设置图例是否重新创建一行</p>

<h3 id="项目传送">项目传送</h3>

<p><a href="https://github.com/octopusy/MPAndroidChart">项目传送门</a></p>]]></content><author><name>Richard</name></author><category term="[&quot;Android&quot;]" /><summary type="html"><![CDATA[android]]></summary></entry><entry><title type="html">几招搞定开源库的封装和发布</title><link href="https://octopusy.github.io//https://octopusy.github.io/2020/06/16/make_jarlib/" rel="alternate" type="text/html" title="几招搞定开源库的封装和发布" /><published>2020-06-16T00:00:00+00:00</published><updated>2020-06-16T00:00:00+00:00</updated><id>https://octopusy.github.io//https://octopusy.github.io/2020/06/16/make_jarlib</id><content type="html" xml:base="https://octopusy.github.io//https://octopusy.github.io/2020/06/16/make_jarlib/"><![CDATA[<h2 id="几招搞定开源库的封装和发布">几招搞定开源库的封装和发布</h2>

<p>开发过程中，我们经常会引用到一些第三方开源包，比如Glide图片库</p>

<p><strong>compile ‘com.github.bumptech.glide:glide:4.3.0’</strong></p>

<p>那么，如何制作自己的开源项目呢？下面让我们来看下制作属于自己开源项目的步骤。</p>

<h3 id="jitpack发布">JitPack发布</h3>

<h4 id="一发布方法">一：发布方法</h4>

<p><strong>本地配置</strong></p>

<p><strong>示例：</strong></p>

<p><img src="/assets/images/Android/JitPack/jitpack01.png" alt="脚本配置" /></p>

<p><strong>库模块的buil.grade开头添加：</strong></p>

<p><img src="/assets/images/Android/JitPack/jitpack02.png" alt="脚本配置" /></p>

<h4 id="二github上创建版本">二：GitHub上创建版本</h4>

<p>代码传好后，在GitHub的【Code】页，选择“releases”进去新建版本。注意版本号要认真填写，例如可以是：v1.0.0</p>

<p>release title和描述可以简单写下release notes，写完后点击“Publish release”就完成了一次版本发布。其实这个时候GitHub帮你做的就是打包。</p>

<p><img src="/assets/images/Android/JitPack/jitpack03.png" alt="脚本配置" /></p>

<p><img src="/assets/images/Android/JitPack/jitpack04.png" alt="脚本配置" /></p>

<h4 id="三jitpack查询">三：JitPack查询</h4>

<p>打开JitPack | Publish JVM and Android libraries，输入GitHub项目地址，例如：octopusy/AndroidLibrary
点击查询，则可以查询到发布的版本列表。</p>

<p><img src="/assets/images/Android/JitPack/jitpack05.png" alt="脚本配置" /></p>

<p><img src="/assets/images/Android/JitPack/jitpack06.png" alt="脚本配置" /></p>

<h4 id="四集成使用">四：集成使用</h4>
<p>选取一个最新的版本，点击“Get it”，下面会在“How to”里出现集成使用方法，非常简单。示例可以参考：octopusy/AndroidLibrary: 易盾验证码android应用嵌入演示</p>

<p>如果在AndroidStudio工程中集成成功，则JitPack上对应的版本Log一栏会出现绿色的图标，否则会出现红色的出错图标，可以点开查看错误信息。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> allprojects {
	repositories {
		...
  		maven { url 'https://jitpack.io' }
 	}
 }


 dependencies {
      implementation 'com.github.octopusy:AndroidLibrary:v1.0.0'
 }
</code></pre></div></div>

<p><img src="/assets/images/Android/JitPack/jitpack07.png" alt="脚本配置" /></p>]]></content><author><name>Richard</name></author><category term="[&quot;Android&quot;]" /><summary type="html"><![CDATA[android,JitPack]]></summary></entry><entry><title type="html">代码规范和团队协作规章</title><link href="https://octopusy.github.io//https://octopusy.github.io/2020/05/14/base_language_rule/" rel="alternate" type="text/html" title="代码规范和团队协作规章" /><published>2020-05-14T00:00:00+00:00</published><updated>2020-05-14T00:00:00+00:00</updated><id>https://octopusy.github.io//https://octopusy.github.io/2020/05/14/base_language_rule</id><content type="html" xml:base="https://octopusy.github.io//https://octopusy.github.io/2020/05/14/base_language_rule/"><![CDATA[<blockquote>
  <p>好的开发习惯，可以帮助我们和团队走的更远</p>
</blockquote>

<h2 id="代码规范">代码规范</h2>

<h3 id="java规范">Java规范：</h3>

<h4 id="一自定义标识符">一、自定义标识符</h4>

<h5 id="1标识符的细节">1、标识符的细节</h5>

<p>① 标识符的组成元素是由字母（a-zA-Z）、数字(0-9) 、下划线(_)、美元符号($)</p>

<p>② 标识符不能以数字开头</p>

<p>③ 标识符是严格区分大小写的</p>

<p>④ 标识符的长度是没有长度限制的</p>

<p>⑤ 标识符的命名一般要有意义（见名知意）</p>

<p>⑥ 关键字、保留字不能用于自定义的标识符</p>

<h5 id="2标识符的规范">2、标识符的规范</h5>

<p>① 类名和接口名单词的首字母大写，其他单词小写（SunTime）</p>

<p>② 变量名与方法名首单词全部小写，其他单词首字母大写，其他单词都要小写 [ doCook() ]</p>

<p>③ 包名全部单词小写</p>

<p>④ 常量全部单词大写，单词与单词之间使用下划线分隔（UP_LENGTH）</p>

<p>3、判断一下那些是符合的标识符</p>

<p>① 12abc_ [不合法。数字不能开头]</p>

<p>② _12abc [合法]</p>

<p>③ $ab12# [不合法。#号不属于标识符组成元素]</p>

<p>④ abc@123 [不合法。@号不属于标识符组成元素]</p>

<h4 id="二注释">二、注释</h4>

<h5 id="1注释的类别">1、注释的类别</h5>

<p>第一种： 单行注释 // 注释的内容</p>

<p>第二种： 多行注释 /* 注释的内容 */</p>

<p>第三种： 文档注释 /** 注释的内容 */ 文档注释也是一个多行注释</p>

<p>注意：单行注释可以嵌套使用，多行注释是不能嵌套使用的</p>

<h5 id="2多行注释与文档注释的区别">2、多行注释与文档注释的区别</h5>

<p>多行注释的内容不能用于生成一个开发者文档，而文档注释的内容可以生产一个开发者文档。</p>

<h5 id="3使用javadoc开发工具即可生成一个开发者文档">3、使用javadoc开发工具即可生成一个开发者文档</h5>

<p>使用格式：javadoc -d 指定存放文档的路径 -version –author(可选) 目标文件</p>

<p>注意细节：</p>

<p>① 如果一个类需要使用javadoc工具生成一个软件的开发者文档，那么该类必须使用public修饰</p>

<p>② 文档注释注释的内容一般都是位于类或者方法的上面的</p>

<p>③ @author 作者 @version 版本 @param 方法的参数 @return 返回值</p>

<h5 id="4规范一般单行注释是位于代码的右侧多行注释与文档注释一般是写在类或者方法的上面的">4、规范：一般单行注释是位于代码的右侧，多行注释与文档注释一般是写在类或者方法的上面的。</h5>

<h3 id="android规范">Android规范</h3>

<h4 id="一-开发注意事项">一： 开发注意事项</h4>

<h5 id="1属性变量方法对象驼峰式命名根据用途定义名称优先用英文次则中文拼音不要出现数字填充命名">1：属性，变量，方法，对象，驼峰式命名，根据用途定义名称，优先用英文，次则中文拼音，不要出现数字填充命名</h5>

<h5 id="2代码要做到重用复用尽量不要写重复的代码">2：代码要做到重用，复用，尽量不要写重复的代码</h5>

<h5 id="3方法和对象要有注释说明">3：方法和对象要有注释说明</h5>

<h5 id="4项目需要国际化的其中涉及到中文的必须写在配置文件中">4：项目需要国际化的，其中涉及到中文的，必须写在配置文件中</h5>

<h5 id="5项目代码里面不要出现原生log打印">5：项目代码里面不要出现原生Log打印</h5>

<h4 id="二android-资源文件命名与使用">二、Android 资源文件命名与使用</h4>
<h5 id="1推荐资源文件需带模块前缀">1. 【推荐】资源文件需带模块前缀。</h5>
<h5 id="2推荐layout-文件的命名方式">2. 【推荐】layout 文件的命名方式。</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Activity 的 layout 以 module_activity 开头

Fragment 的 layout 以 module_fragment 开头

Dialog 的 layout 以 module_dialog 开头

include 的 layout 以 module_include 开头

ListView 的行 layout 以 module_list_item 开头

RecyclerView 的 item layout 以 module_recycle_item 开头

GridView 的行 layout 以 module_grid_item 开头
</code></pre></div></div>

<h5 id="3推荐-drawable-资源名称以小写单词下划线的方式命名根据分辨率">3. 【推荐】 drawable 资源名称以小写单词+下划线的方式命名，根据分辨率</h5>

<p>不同存在不同的 mipmap目录下，建议只使用一套，例如 mipmap-xhdpi。</p>

<p>采用规则如模块名_业务功能描述_控件描述_控件状态限定词 如：module_login_btn_pressed,module_tabs_icon_home_normal</p>

<h5 id="4推荐anim-资源名称以小写单词下划线的方式命名">4. 【推荐】anim 资源名称以小写单词+下划线的方式命名，</h5>

<p>采用以下规则： 模  块名_逻辑名称_[方向|序号]</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tween 动画资源 ： 尽可能以通用的动画名称命名，
如  module_fade_imodule_fade_out , module_push_down_in (动画+方向)；

frame 动画资源：尽可能以模 块+功能命名+序号。如：module_loading_grey_0
</code></pre></div></div>
<h5 id="5推荐-color-资源使用aarrggbb-格式">5. 【推荐】 color 资源使用#AARRGGBB 格式</h5>

<p>写入 module_colors.xml，文件名格式采用以下规则： 模块名_逻辑名称_颜色 如： #33b5e5e5</p>

<h5 id="6推荐dimen-资源以小写单词下划线方式命名">6. 【推荐】dimen 资源以小写单词+下划线方式命名</h5>

<p>写入module_dimens.xml 文件中采用以下规则： 模块名_描述信息 如： 1dp</p>

<h5 id="7推荐style-资源采用小写单词下划线方式命名">7. 【推荐】style 资源采用小写单词+下划线方式命名</h5>

<p>写入module_styles.xml 文件中采用以下规则： 父 style 名称.当前 style 名如：</p>

<p>…</p>

<h5 id="8推荐string资源文件或者文本用到字符需要全部写入">8. 【推荐】string资源文件或者文本用到字符需要全部写入</h5>
<p>module_strings.xml文件中字符串以小写单词+下划线的方式命名，采用以下规则： 模块名_逻辑名称 如：moudule_login_tips,module_homepage_notice_desc</p>

<h5 id="9推荐id-资源原则上以驼峰法命名">9. 【推荐】Id 资源原则上以驼峰法命名</h5>

<p>View 组件的资源 id 需要以 View 的缩写作前缀。常用缩写表如下： 控件 缩写</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LinearLayout ll

RelativeLayout rl

ConstraintLayout cl

ListView lv

ScollView sv

TextView tv

Button btn

ImageView iv

CheckBox cb

RadioButton rb

EditText et
</code></pre></div></div>

<h4 id="二android-资源文件命名与使用-1">二、Android 资源文件命名与使用</h4>

<p>其它控件的缩写推荐使用小写字母并用下划线进行分割，例如： ProgressBar 对应的缩写为 progress_bar DatePicker 对应的缩写为 date_picker</p>

<p>【推荐】大分辨率图片（单维度超过 1000）大分辨率图片建议统一放在 xxhdpi</p>

<p>目下管理，否则将导致占用内存成倍数增加。 说明： 为了支持多种屏幕尺寸和密度，Android 为多种屏幕提供不同的资源目录进行适配为不同屏幕密度提供不同的位图可绘制对象，可用于密度特定资源的配置限定符下面详述） 包括 ldpi（低）、mdpi（中）、 hdpi（高）、xhdpi（超高）、xxhdpi 超高）和 xxxhdpi（超超超高）。例如，高密度屏幕的位图应使用 mipmap-hd根据当前的设备屏幕尺寸和密度，将会寻找最匹配的资源，如果将高分辨率图片入低密度目录，将会造成低端机加载过大图片资源，又可能造成 OOM，同时也是源浪费，没有必要在低端机使用大图。 正例： 将 144<em>144 的应用图标 PNG 文件放在 mipmap-xxhdpi 目录 反例： 将 144</em>144 的应用图标 PNG 文件放在 mipmap-mhdpi 目录</p>

<h4 id="三其它规范">三：其它规范</h4>

<h5 id="间距规范">间距规范 </h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>一栏上边距24dp

普通大间距16dp

分割线0.5dp

中框左右边距16dp

文字上下边距4dp

左右边距8dp

默认view高度48dp

区域分割线8dp

字体排版：

大标题34sp

标题20sp

副标题16sp

正文14sp

辅助信息12sp

提示文字7sp

按钮文字14sp

常用字号：

12sp: 小字提示

14sp(桌面端13sp): 正文/按钮文字

16sp(桌面端13sp)：小标题

20sp: AppBar文字

24sp: 大标题

34sp/45sp/56sp/112sp/超大号文字

所有可操作元素最小点击区域尺寸：48dp*48dp

栅格系统的最小单位是8dp,一切距离，尺寸都应该是8dp的整数倍，以下是一些常见的尺寸与距离：

·顶部状态栏高度：24dp

·AppBar最小高度：56dp

·底部导航栏高度：48dp

·悬浮按钮尺寸：56*56dp/40*40dp

·用户头像尺寸：64*64dp/40*40dp

·小图标点击区域：48*48dp

·侧边抽屉到屏幕右边的距离：56dp

·卡片间距：8dp

·分割线上下留白：8dp

·大多数元素的留白距离：16dp

·屏幕左右对齐基线：16dp

·文字左侧对齐基线：72dp
</code></pre></div></div>

<h2 id="团队协同开发规章">团队协同开发规章</h2>

<h3 id="一事先约定">一：事先约定</h3>

<p>约定绝对是提高团队协作最有用的方式之一。大家先约定好一些规则，然后接下来各自干各自的，并且遵循着这个约定，这样便大大提高了效率。 一个项目可以约定的东西有很多，比如约定一种 <a href="https://link.zhihu.com/?target=https%3A//github.com/zhanzizhen/coding-blog/issues/18">分支策略</a>，约定一种代码风格（通过 eslint 插件来执行），约定接口文档等等。</p>

<h4 id="1统一工具和版本">1：统一工具和版本</h4>

<p>“工欲善其事必先利其器”，统一团队成员开发工具和依赖插件版本是一件非常重要的事情，可以避免后期大量的项目合并问题。减少项目开发周期。</p>

<h4 id="2命名约定">2：命名约定</h4>

<p>命名冲突是开发过程中常见的一个问题，特别是在多人协作开发的项目中。团队协作开发一般会以模块进行进行划分，在命名时，可以通过加上模块名称作为前缀或者后缀，可以避免大部分的命名冲突问题，大大提示团队开发的效率。</p>

<h3 id="二统一管理">二：统一管理</h3>

<h4 id="1善用代码版本管理工具">1：善用代码版本管理工具</h4>

<p>在团队开发过程中，必须约定每日统一上传合并代码，每个团队成员必须单独创建一个分支进行开发，自测试通过后再合并到主分支上，这样可以避免后期花费大量的时间排查和解决问题。</p>]]></content><author><name>Richard</name></author><category term="[&quot;协同开发&quot;]" /><summary type="html"><![CDATA[代码规范]]></summary></entry><entry><title type="html">Android透明度设置详解</title><link href="https://octopusy.github.io//https://octopusy.github.io/2019/04/03/transparent/" rel="alternate" type="text/html" title="Android透明度设置详解" /><published>2019-04-03T00:00:00+00:00</published><updated>2019-04-03T00:00:00+00:00</updated><id>https://octopusy.github.io//https://octopusy.github.io/2019/04/03/transparent</id><content type="html" xml:base="https://octopusy.github.io//https://octopusy.github.io/2019/04/03/transparent/"><![CDATA[<h2 id="android透明度设置详解">Android透明度设置详解</h2>

<h2 id="本节前言">本节前言</h2>
<blockquote>
  <p>今天给大家介绍的是关于Android各式各样的透明度，有需要的希望能够帮到你们,我们在学习本节课之前，先来介绍一下万能的颜色透明度</p>
</blockquote>

<h3 id="颜色透明度">颜色透明度</h3>
<p>格式：</p>

<p><code class="language-plaintext highlighter-rouge">android:background="#XXxxxxxx"（颜色可以写在color中）</code></p>

<p>说明：半透明颜色值不同于平时使用的颜色，半透明颜色值共8位，前2位是透明度，后6位是颜色。也就是说透明度和颜色结合就可以写出各种颜色的透明度。下面是透明度说明表，供大家参考。</p>

<table>
  <thead>
    <tr>
      <th>透明度</th>
      <th>百分比</th>
      <th>前缀</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>不透明</td>
      <td>100%</td>
      <td>FF</td>
    </tr>
    <tr>
      <td> </td>
      <td>95%</td>
      <td>F2</td>
    </tr>
    <tr>
      <td> </td>
      <td>90%</td>
      <td>E6</td>
    </tr>
    <tr>
      <td> </td>
      <td>85%</td>
      <td>D9</td>
    </tr>
    <tr>
      <td> </td>
      <td>80%</td>
      <td>CC</td>
    </tr>
    <tr>
      <td> </td>
      <td>75%</td>
      <td>BF</td>
    </tr>
    <tr>
      <td> </td>
      <td>70%</td>
      <td>B3</td>
    </tr>
    <tr>
      <td> </td>
      <td>65%</td>
      <td>A6</td>
    </tr>
    <tr>
      <td> </td>
      <td>60%</td>
      <td>99</td>
    </tr>
    <tr>
      <td> </td>
      <td>55%</td>
      <td>8C</td>
    </tr>
    <tr>
      <td>半透明</td>
      <td>50</td>
      <td>80</td>
    </tr>
    <tr>
      <td> </td>
      <td>45%</td>
      <td>73</td>
    </tr>
    <tr>
      <td> </td>
      <td>40%</td>
      <td>66</td>
    </tr>
    <tr>
      <td> </td>
      <td>35%</td>
      <td>59</td>
    </tr>
    <tr>
      <td> </td>
      <td>30%</td>
      <td>4D</td>
    </tr>
    <tr>
      <td> </td>
      <td>25%</td>
      <td>40</td>
    </tr>
    <tr>
      <td> </td>
      <td>20%</td>
      <td>33</td>
    </tr>
    <tr>
      <td> </td>
      <td>15%</td>
      <td>26</td>
    </tr>
    <tr>
      <td> </td>
      <td>10%</td>
      <td>1A</td>
    </tr>
    <tr>
      <td> </td>
      <td>5%</td>
      <td>0D</td>
    </tr>
    <tr>
      <td>全透明</td>
      <td>0</td>
      <td>00</td>
    </tr>
  </tbody>
</table>

<p><strong>部分透明度示例：</strong></p>

<ul>
  <li>全透明：#00000000</li>
  <li>半透明：#80000000</li>
  <li>不透明：#FF000000</li>
  <li>白色半透明：#80FFFFFF</li>
  <li>红色30%透明：#4Dca0d0d</li>
</ul>

<h3 id="控件透明度">控件透明度</h3>

<ol>
  <li><strong>Java代码实现设置透明度</strong></li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>text = (TextView) findViewById(R.id.text);
text.getBackground().setAlpha(12);
</code></pre></div></div>

<p>setAlpha()的括号中可以填0–255之间的数字。数字越大，越不透明。</p>

<blockquote>
  <p>注：这里需要注意的是，控件必须是在最外层布局里面，如果直接设置最外层布局会出错</p>
</blockquote>

<p><strong>注意点：</strong>
在5.0以上系统时，有些机型会出现莫名其妙的颜色值不起作用，变成透明了，也就是用此方法会导致其他共用一个资源的布局（例如：@color/white）透明度也跟着改变。比如text用上述方法设置成透明后，项目中，其他用到text颜色值的控件，都变成透明了。</p>

<p>原因：在布局中多个控件同时使用一个资源的时候，这些控件会共用一个状态，例如ColorState，如果你改变了一个控件的状态，其他的控件都会接收到相同的通知。这时我们可以使用mutate()方法使该控件状态不定，这样不定状态的控件就不会共享自己的状态了。</p>

<ol>
  <li>在xml布局中进行设置</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;TextView
    android:text="Hello World!"
    android:background="#987654"
    android:layout_width="match_parent"
    android:alpha="0.5"
    android:layout_height="100dp" /&gt;
</code></pre></div></div>

<p>android:alpha的值为0~1之间的数。数字越大，越不透明。1表示完全不透明，0表示完全透明。</p>

<ol>
  <li>通过颜色透明度进行设置</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;TextView
        android:id="@+id/text"
        android:text="Hello World!"
        android:background="#80987654"
        android:layout_width="match_parent"
        android:layout_height="100dp" /&gt;
</code></pre></div></div>

<h3 id="activity透明">Activity透明</h3>

<p>说道Activity透明，发现网上的基本上都已经过时，在有v7以上的控件都无法实现，均会报错</p>
<blockquote>
  <p>You need to use a Theme.AppCompat theme (or descendant) with the design library.</p>
</blockquote>

<p>所以如若你的布局xml文件有 support-V7 上的控件的话，<style name="translucent">里的name要前要添加 AppTheme，如：</style></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;style name=" AppTheme.translucent"&gt;
</code></pre></div></div>

<ol>
  <li>方法一：</li>
</ol>

<ul>
  <li>在 res/values/color.xml 文件下加入一个透明颜色值，这里的 color 参数，是两位数一个单位，前两位数是透明度（16进制：00 – FF，最大为256，数值越低越透明），后面每两位一对是16进制颜色数字，示例中为白色。</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;resources&gt;
   &lt;color name="translucent_background"&gt;#80000000&lt;/color&gt;
&lt;/resources&gt;
</code></pre></div></div>

<ul>
  <li>在 res/values/styles.xml 文件中加入一个自定义样式，代码如下。</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;!-- item name="android:windowBackground"         设置背景透明度及其颜色值 --&gt;
&lt;!-- item name="android:windowIsTranslucent"      设置当前Activity是否透明--&gt;
&lt;!-- item name="android:windowAnimationStyle"     设置当前Activity进出方式--&gt;
&lt;style name="translucent"&gt;
    &lt;item name="android:windowBackground"&gt;@color/translucent_background&lt;/item&gt;
    &lt;item name="android:windowIsTranslucent"&gt;true&lt;/item&gt;
    &lt;item name="android:windowAnimationStyle"&gt;@android:style/Animation.Translucent&lt;/item&gt;
&lt;/style&gt;
</code></pre></div></div>

<ol>
  <li>方法二：</li>
</ol>

<ul>
  <li>在Activity的布局xml的根标签中写入透明颜色：</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>android:background="#80000000"
</code></pre></div></div>

<ul>
  <li>在 AndroidManifest.xml 找到要实现透明的 Activity，在想要实现透明的 Activity 中配置其属性，如下：</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>android:theme="@android:style/Theme.Translucent.NoTitleBar"
</code></pre></div></div>]]></content><author><name>Richard</name></author><category term="[&quot;Android&quot;]" /><summary type="html"><![CDATA[android]]></summary></entry><entry><title type="html">Android第三方依赖区别</title><link href="https://octopusy.github.io//https://octopusy.github.io/2019/04/02/comiple/" rel="alternate" type="text/html" title="Android第三方依赖区别" /><published>2019-04-02T00:00:00+00:00</published><updated>2019-04-02T00:00:00+00:00</updated><id>https://octopusy.github.io//https://octopusy.github.io/2019/04/02/comiple</id><content type="html" xml:base="https://octopusy.github.io//https://octopusy.github.io/2019/04/02/comiple/"><![CDATA[<h2 id="android第三方包依赖区别">android第三方包依赖区别</h2>

<h3 id="compileimplementationapi">compile（implementation，api）</h3>
<p>这种是我们最常用的方式，使用该方式依赖的库将会参与编译和打包。</p>

<h4 id="implementation该依赖方式所依赖的库不会传递只会在当前module中生效">implementation：该依赖方式所依赖的库不会传递，只会在当前module中生效。</h4>

<h4 id="api该依赖方式会传递所依赖的库当其他module依赖了该module时可以使用该module下使用api依赖的库">api：该依赖方式会传递所依赖的库，当其他module依赖了该module时，可以使用该module下使用api依赖的库。</h4>

<blockquote>
  <p>当我们依赖一些第三方的库时，可能会遇到com.android.support冲突的问题，就是因为开发者使用的compile或api依赖的com.android.support包与我们本地所依赖的com.android.support包版本不一样，所以就会报All com.android.support libraries must use the exact same version specification (mixing versions can lead to runtime crashes这个错误。</p>
</blockquote>

<p>解决办法：</p>
<ul>
  <li>修改自己项目中的com.android.support的版本号，与所依赖的库版本号一致，但是当我们依赖的库中的com.android.support版本号有好几个版本就不行了。（不推荐）</li>
  <li>依赖第三方库时候排除掉对com.android.support包的依赖，这样自己的项目随便依赖什么版本都可以，但是这种方法需要你先找到哪些库存在冲突</li>
  <li>通过groovy脚本强制修改冲突的依赖库版本号 （推荐）</li>
  <li>将项目迁移至AndroidX（推荐）</li>
</ul>

<h3 id="providedcompileonly">provided（compileOnly）</h3>
<p>只在编译时有效，不会参与打包，可以在自己的moudle中使用该方式依赖一些比如com.android.support，gson这些使用者常用的库，避免冲突。</p>

<h3 id="apkruntimeonly">apk（runtimeOnly）</h3>
<p>只在生成apk的时候参与打包，编译时不会参与，很少用。</p>

<h3 id="testcompiletestimplementation">testCompile（testImplementation）</h3>
<p>testCompile 只在单元测试代码的编译以及最终打包测试apk时有效。</p>

<h3 id="debugcompiledebugimplementation">debugCompile（debugImplementation）</h3>
<p>debugCompile 只在debug模式的编译和最终的debug apk打包时有效</p>

<h3 id="releasecompilereleaseimplementation">releaseCompile（releaseImplementation）</h3>
<p>Release compile 仅仅针对Release 模式的编译和最终的Release apk打包。</p>]]></content><author><name>Richard</name></author><category term="[&quot;Android&quot;]" /><summary type="html"><![CDATA[android]]></summary></entry><entry><title type="html">思维转换</title><link href="https://octopusy.github.io//https://octopusy.github.io/2018/12/03/thinking-transfer/" rel="alternate" type="text/html" title="思维转换" /><published>2018-12-03T00:00:00+00:00</published><updated>2018-12-03T00:00:00+00:00</updated><id>https://octopusy.github.io//https://octopusy.github.io/2018/12/03/thinking-transfer</id><content type="html" xml:base="https://octopusy.github.io//https://octopusy.github.io/2018/12/03/thinking-transfer/"><![CDATA[<h2 id="思维转换">思维转换</h2>

<blockquote>
  <p>有时候错的不是世界，是你理解世界的思维方式。每个人都在以他的理解力和经历，构建自己的思维模式，然后在用这个思维模式来理解这个世界。</p>
</blockquote>

<p><strong>思维模式，影响着你和这个世界的关系，决定着你在这个世界的位置</strong></p>

<p>电影《1942》里，张国立饰演的东家，说了这么一句话：“只要活着到陕西。给我十年，我还是地主。”</p>

<p>他的信心，是对自己思维模式的信心。前一段时间一直流行说穷人思维和富人思维，东家就是富人思维。</p>

<p>我们想改变自己，如果我们只想发生较小的变化，那么专注于自己的态度和行为就可以，比如空杯思维；如果发生实质性的变化，那就需要“思维转化”，可能连被子都换掉。
去除自己的穷人思维，换上富人思维，从根本上改变自己。</p>

<p>二战期间，犹太裔心理学家维克托弗兰克在纳粹集中营发现，即便是最走投无路的时候，人也可以选择用积极的心态来活着。无法控制外界客观，但你可以控制你自己。有这样的思维模式，他度过了这个难关。</p>

<p><strong>打开思维转换开关的两个建议</strong></p>

<p><strong>第一，多读书，多交友，多旅行</strong></p>

<p>每本书都是一套“思维模式”，读书可以理解不同的思维模式。人丑多读书。认识人越多，也会见识更多的思维模式。不要独自吃午饭，只要名单上还有人，一个人吃午饭就是可耻的，你损失了理解别人思维模式的机会。一直是和部门同事吃午饭，可能失去了理解更多人的机会。</p>

<p>旅行也能碰到更多的完全迥异的思维模式。
其实，多读书、多交友、多旅行，就是长见识，开阔眼界，看到更多与自己完全不同的思维模式，让自己有机会思考和反省自己的思维模式。</p>

<p><strong>第二，把自己放进别人的鞋子里</strong></p>

<p>有这样一个真实的故事，一个中年男人带了几个男孩上车这几个男孩特别调皮，弄的车上人都很反感，这时候旁边一个人跟中年男子说，可不可以管管你们家的孩子，这个中年男子说，对不起她们的母亲刚刚去世，我有点措手不及估计孩子们也是.有时候站在别人的角度上去思考问题，或许你看到的问题就没那么难理解了。</p>

<p>生活和工作中的每次争论，也是特别好练习“思维转换”的机会。试着用对方的观点说服自己，是一种同理心的转换。把自己放进别人的鞋子里，你才会明白，你过去捍卫的是自己的观点，还仅仅是自己的尊严。很多时候，我们的争论是建立在要赢的目标上，失去了分辨事实的目的。在工作生活中，我们争论的目的应该是找出解决方案、找出真相等实际意义，所以不妨在争论的过程中，用对方的角度想一想。</p>

<p><strong>总结</strong></p>

<p><strong>如果我们只想发生较小的变化，那么专注于自己的态度和行为就可以，比如空杯思维；如果发生实质性的变化，那就需要“思维转化”，可能连被子都换掉。</strong></p>]]></content><author><name>Richard</name></author><category term="[&quot;碎碎念&quot;]" /><summary type="html"><![CDATA[思考]]></summary></entry><entry><title type="html">LeakCanary Android内存泄漏检测利器</title><link href="https://octopusy.github.io//https://octopusy.github.io/2018/08/10/leakCanary/" rel="alternate" type="text/html" title="LeakCanary Android内存泄漏检测利器" /><published>2018-08-10T00:00:00+00:00</published><updated>2018-08-10T00:00:00+00:00</updated><id>https://octopusy.github.io//https://octopusy.github.io/2018/08/10/leakCanary</id><content type="html" xml:base="https://octopusy.github.io//https://octopusy.github.io/2018/08/10/leakCanary/"><![CDATA[<h2 id="leakcanary--android内存泄漏检测利器">LeakCanary  Android内存泄漏检测利器</h2>

<blockquote>
  <p>内存泄露是Android开发中常见的问题, 经常会造成应用闪退，降低程序的稳定性和用户体验。
LeakCanary是一个专门用来检测内存泄漏的工具，可以帮助我们在应用开发过程中检查出页面的泄露问题, 并提供内存泄漏的具体位置，快速的帮助我们定位和解决问题。</p>
</blockquote>

<h3 id="leakcanary-集成检测效果">LeakCanary 集成检测效果</h3>

<p><img src="http://pey51suf1.bkt.clouddn.com/page_01.png" alt="AndroidP" />     <img src="http://pey51suf1.bkt.clouddn.com/page_02.png" alt="AndroidP" /></p>

<h2 id="为什么需要leakcanary">为什么需要LeakCanary？</h2>

<p>由于LeakCanary简单，易于发现问题，人人可参与。</p>

<ul>
  <li>
    <p>简单：只需设置一段代码即可，打开应用运行一下就能够发现内存泄露。而MAT分析需要Heap Dump，获取文件，手动分析等多个步骤。</p>
  </li>
  <li>
    <p>易于发现问题：在手机端即可查看问题即引用关系，而MAT则需要你分析，找到Path To GC Roots等关系。</p>
  </li>
  <li>
    <p>人人可参与：开发人员，测试测试，产品经理基本上只要会用App就有可能发现问题。而传统的MAT方式，只有部分开发者才有精力和能力实施。</p>
  </li>
</ul>

<h3 id="如何集成">如何集成</h3>

<h4 id="1在-buildgradle-中添加依赖">1.在 build.gradle 中添加依赖</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
</code></pre></div></div>

<h4 id="2在应用中引用leakcanary">2.在应用中引用LeakCanary</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>private RefWatcher mRefWatcher;

@Override
public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
        // This process is dedicated to LeakCanary for heap analysis. release模式下不引用
        return;
    }
    mRefWatcher = LeakCanary.install(this);
}

public static RefWatcher getRefWatcher() {
    return sRefWatcher;
}
</code></pre></div></div>

<h4 id="3在activity中模拟一个泄漏">3.在Activity中模拟一个泄漏</h4>

<ul>
  <li><strong>在Actvity中引用一个单例来修改TextView控件的值</strong></li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TextView signature = (TextView) findViewById(R.id.signature);
XXXHelper.getInstance(this).setRetainedTextView(signature);
</code></pre></div></div>

<ul>
  <li><strong>单例对象</strong></li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import android.content.Context;
import android.widget.TextView;
import android.widget.Toast;

public class XXXHelper {

    private Context mCtx;
    private TextView mTextView;

    private static XXXHelper ourInstance = null;

    public static XXXHelper getInstance(Context context) {
        if (ourInstance == null) {
            ourInstance = new XXXHelper(context);
        }
        return ourInstance;
    }

    public void setRetainedTextView(TextView tv){
        this.mTextView = tv;
        mTextView.setText(mCtx.getString(android.R.string.ok));
        Toast.makeText(mCtx,mCtx.getString(android.R.string.ok),Toast.LENGTH_SHORT).show();
    }

    private XXXHelper() {
    }

    private XXXHelper(Context context) {
        this.mCtx = context;
    }

}
</code></pre></div></div>

<ul>
  <li><strong>内存泄漏原因</strong></li>
</ul>

<p>在单例中引用了Activity的Context对象和TextView控件,并长久持有，如果Activity被回收，但是单例中的Context和TextView控件却不会被回收，一直占据在内存中，反复进出几次Activity,就很容易导致内存泄漏了。</p>

<ul>
  <li><strong>内存泄漏解决办法</strong></li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 删除引用, 防止泄露
public void removeRetained() {
    mTextView = null;
    mCtx = null;
}
</code></pre></div></div>

<p>在Activity的destory() 方法中引用removeRetained()方法,释放对象的引用</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Override protected void onDestroy() {
    super.onDestroy();
    // 防止内泄露
    XXXHelper.getInstance(this.getApplication()).removeRetained();
}
</code></pre></div></div>

<ul>
  <li><strong>监控某个可能存在内存泄露的对象</strong></li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>AppApplication.getRefWatcher().watch(sLeaky);
</code></pre></div></div>

<h3 id="检测对象">检测对象</h3>

<ul>
  <li><strong>Activity</strong></li>
  <li><strong>Fragment</strong></li>
  <li><strong>BroadcastReceiver</strong></li>
  <li><strong>Service</strong></li>
  <li><strong>其他有生命周期的对象</strong></li>
  <li><strong>直接间接持有大内存占用的对象（即Retained Heap值比较大的对象）</strong></li>
</ul>

<h4 id="检测时机">检测时机</h4>

<p>由于内存泄露是因为<strong>某个对象在该释放的时候由于被其他对象持有没有被释放</strong>，因此，我们监控需要设置在<strong>对象（很快）被释放的时候</strong>，如Activity和Fragment的onDestroy方法。</p>

<h3 id="常用的解决内存泄漏的方法">常用的解决内存泄漏的方法</h3>

<ul>
  <li>尽量使用Application的Context而不是Activity的</li>
  <li>使用弱引用或者软引用</li>
  <li>手动设置null，解除引用关系</li>
  <li>将内部类设置为static，不隐式持有外部的实例</li>
  <li>注册与反注册成对出现，在对象合适的生命周期进行反注册操作。</li>
  <li>如果没有修改的权限，比如系统或者第三方SDK，可以使用反射进行解决持有关系</li>
</ul>

<p><strong>注意</strong></p>

<p>目前LeakCanary一次只能报一个泄漏问题，如果存在内存泄漏但不是你的模块，并不能说明这个模块没有问题。建议建议将非本模块的泄漏解决之后，再进行检测。</p>]]></content><author><name>Richard</name></author><category term="[&quot;第三方开源框架&quot;]" /><summary type="html"><![CDATA[LeakCanary 内存检测]]></summary></entry><entry><title type="html">如何快速适配AndroidP 刘海屏</title><link href="https://octopusy.github.io//https://octopusy.github.io/2018/08/06/android8.1-fecture/" rel="alternate" type="text/html" title="如何快速适配AndroidP 刘海屏" /><published>2018-08-06T00:00:00+00:00</published><updated>2018-08-06T00:00:00+00:00</updated><id>https://octopusy.github.io//https://octopusy.github.io/2018/08/06/android8.1-fecture</id><content type="html" xml:base="https://octopusy.github.io//https://octopusy.github.io/2018/08/06/android8.1-fecture/"><![CDATA[<h2 id="刘海屏适配方案">刘海屏适配方案</h2>

<p>智能手机发展至今，边框越做越窄，屏幕中横比越做越大。而凹口屏 (又称 “刘海屏”) 更是成为各大设备厂商手中的”神兵利器”：既能让用户享受到全面屏体验，又能预留出足够空间安装感应器。目前，已经有 11 家厂商相继发布了 16 款带有 “刘海” 设计的手机，其中部分机型为 Android P Beta 设备。预计今后会有更多凹口屏设备与消费者见面。</p>

<p>凹口屏一方面为开发者创造了绝好的条件，展示各自应用的独特魅力；另一方面，它又凸显了适配的重要性，不论设备拥有一个还是两个屏幕缺口，采用的是 18:9 亦或是其它尺寸的屏幕，开发者需要保证应用在所有设备上都能够提供相同的用户体验。</p>

<p><img src="http://pey51suf1.bkt.clouddn.com/androidp.jpg" alt="AndroidP" /></p>

<center>△ 凹口屏设备: Essential PH-1 (左) 和华为 P20 (右)</center>

<h3 id="凹口屏幕适配方案">凹口屏幕适配方案</h3>

<p>随着各大设备厂商陆续跻身凹口屏大军之列，开发者应该如何正确应对，确保应用能够快速适配呢？</p>

<p>好消息是：即使在凹口屏设备上，大部分应用内容并不会受到影响。默认情况下，如果开发者在竖屏模式下未对状态栏设定任何特殊标志位，状态栏会根据屏幕缺口情况自行调整高度 (缺口高度 ≤ 状态栏高度)，而应用内容则会显示在状态栏以下区域；在横屏和全屏模式下，系统会在应用窗口四周保留黑边，避开在缺口区显示应用内容。</p>

<p>不过，为避免应用在凹口屏上出现适配问题，开发者还需注意以下几点：
不要将状态栏高度设置为固定值，否则很容易出现问题。在条件允许的情况下，可以调用 WindowInsetsCompat 获取状态栏高度；
在全屏模式下，由于系统在应用周围保留了黑边，因此画面不会占满整个屏幕，此时开发者需要谨慎考虑，窗口坐标或屏幕坐标之间作出抉择。比如说，如果您调用了 MotionEvent.getRawX/Y() 来获取触摸点触相对于屏幕原点坐标，请别忘了使用getLocationOnScreen() 将它们转换为视图坐标；
请特别注意应用在进入和退出全屏模式时的视图转换问题。</p>

<p>请查阅《<a href="https://developer.android.com/guide/topics/display-cutout/#best_practices_for_display_cutout_support">屏幕缺口支持指南</a>》，了解适配过程中您可能遇到的问题以及相应解决方案。</p>

<h3 id="灵活利用缺口区域">灵活利用缺口区域</h3>

<p>通过在缺口区域显示应用内容，尤其是视频、图片、地图或者游戏一类的内容，开发者能够显著提升应用沉浸度，为用户打造真正的全面屏体验。</p>

<p><img src="http://pey51suf1.bkt.clouddn.com/androidp_map.jpg" alt="Android缺口" /></p>

<center>
△ 应用请求在缺口区域进行布局
</center>

<p>开发者可以调用 Android P 中的相关 API，判断设备是否具有凹口屏，然后在获取缺口的位置信息，并管理内容在缺口区域的布局。</p>

<p>您可以利用全新的窗口布局属性 layoutInDisplayCutoutMode 管理应用在凹口屏幕上的布局显示。在默认情况下，只有当缺口完全包含在状态栏内时，系统才会允许应用延伸至缺口区域，否则窗口不会与缺口重叠。此外，您可以通过更改 layoutInDisplayCutoutMode 参数，让设备始终 (或绝不) 允许应用使用缺口区域。如果您希望利用到整个显示区域，而且不介意缺口位置无法显示应用内容，SHORT_EDGES 模式是个不错的选择，在该模式下，系统始终允许应用窗口延伸至缺口区域。</p>

<p>在适配过程中，您可调用 getDisplayCutout() 获取无显示凹口区的外边距和包围盒值，并利用这些数值判断应用内容是否与缺口重合，以及是否需要重新调整内容位置。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;style name="ActivityTheme"&gt;
   &lt;item name="android.windowLayoutInDisplayCutoutMode"&gt;
     default/shortEdges/never
   &lt;/item&gt;
&lt;/style&gt;
</code></pre></div></div>

<center>
△ 活动主题中的窗口布局属性: layoutInDisplayCutoutMode
</center>

<p>此外，我们已经将 activity 主题中的 layoutInDisplayCutoutMode 属性往后添加到 Android 8.1 (API 27) 的设备上，您也可以在缺口区域对应用内容进行布局。不过若系统版本为 Android 8.1 或更低，设备是否具有凹口屏支持则取决于厂商。</p>

<p>如果应用需要针对多个 API 等级进行内容布局，您可以通过 SDK manager 下载 AndroidX 库，并使用库中的 DisplayCutoutCompat 来简化管理流程。</p>

<h3 id="针对凹口屏测试您的应用">针对凹口屏测试您的应用</h3>

<p>强烈建议您对应用的所有界面和操作进行测试，确保应用在凹口屏设备上能够流畅运行。建议您选择一款配有凹口屏的 Android P Beta 设备作为调试设备，如 Essential PH-1。</p>

<p><img src="http://pey51suf1.bkt.clouddn.com/androidp_system.jpg" alt="Android缺口" /></p>

<p>如果您暂时没有条件进行真机测试，您可以在非凹口屏 P 版本手机或者 Android 模拟器中，开启 “模拟具有凹口的显示屏” 的设置项，然后再进行调试。这能帮助您发现应用在凹口屏设备上运行时可能会遇到的问题，不论设备的系统版本是 Android 8.1 还是 Android P。</p>

<h3 id="凹口屏幕全知道">凹口屏幕全知道</h3>

<p>Android P 中引入了对凹口屏幕的官方平台支持，同时提供了一系列 API 帮助开发者在缺口区内外对应用内容进行布局。为了保障一致性与应用兼容性，我们目前正在与设备合作伙伴展开积极合作，携手制定相关行业标准。</p>

<p>首先，厂商需要确保设备的凹口屏幕不会对应用造成不良影响，这涉及到以下两项关键要求：</p>

<ul>
  <li>在竖屏模式下，若没有设定特殊标志位，状态栏高度必须大于或等于缺口高度；</li>
  <li>在全屏或横屏模式下，缺口区域必须整个落在黑色填充区内。</li>
</ul>

<p>第二点，屏幕每条短边上缺口数量不可超过 1，即：</p>
<ul>
  <li>不允许一条短边上存在 2 个或 2 个以上缺口，即每台设备最多拥有 2 个屏幕缺口；</li>
  <li>不允许在屏幕左侧或右侧出现缺口。</li>
</ul>

<p>在满足以上条件的前提下，厂商可以按照各自需求，设计缺口位置和形状。</p>

<h3 id="特殊模式">特殊模式</h3>

<p>在某些运行 Android 8.1 (API 等级 27) 或更早版本的设备上，用户可以通过开启 “特殊模式”，允许系统在全屏或横屏模式下延伸应用窗口至缺口区域。用户一般可以在导航栏中找到并勾选该模式，接着系统会弹出一个确认对话框，在征得用户同意后，模式才会正式生效。</p>

<p><img src="http://pey51suf1.bkt.clouddn.com/androidp_landspace.jpg" alt="Android缺口" /></p>

<center>
△ 提供 "特殊模式" 选项的设备允许用户将应用窗口延伸至缺口区域 (若应用支持在缺口区域显示)
</center>

<p>如果应用的 targetSdkVersion 为 27 或更高，在必要时您可以通过更改活动主题中的layoutInDisplayCutoutMode 属性，退出特殊模式。</p>

<h3 id="别忘了-为长屏幕设备做好准备">别忘了: 为长屏幕设备做好准备</h3>

<p>在适配凹口屏的同时，您不妨考虑一下如何确保应用在长屏幕设备上 (纵横比大于或等于 18:9) 也能够正常运行，尤其是现在市面上长屏手机越来越多，而且这些设备往往同时还采用了凹口屏设计。</p>

<p>强烈建议您选择灵活的适配方案，确保应用不受运行设备所限，高效利用全部显示区域。您可以针对不同屏幕尺寸进行相应的兼容性测试，以确保应用在功能和视图方面都表现良好。</p>

<p>建议查阅《<a href="https://mp.weixin.qq.com/s/mSD0Wup5gHZr19VPmpXD-g">长屏幕设备适配指南</a>》和《<a href="https://mp.weixin.qq.com/s/mSD0Wup5gHZr19VPmpXD-g">如何针对长屏幕设备优化您的应用</a>》一文中列出的几项建议，进行相应开发。如果您的应用无法适应长屏幕的纵横比，您可以通过设置应用的最大支持纵横比，要求系统用黑色填充应用边缘的显示空间。</p>

<p>希望以上内容能对您有所帮助，让您不惧 “刘海”，只为更好体验！</p>

<p>《<a href="https://mp.weixin.qq.com/s/mSD0Wup5gHZr19VPmpXD-g">阅读原文</a>》</p>]]></content><author><name>Richard</name></author><category term="[&quot;Android&quot;]" /><summary type="html"><![CDATA[AndroidP 刘海屏]]></summary></entry></feed>