Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all 5930 articles
Browse latest View live

Gradle for Android(四)

$
0
0

## 第四篇( 构建变体 )
当你在开发一个app,通常你会有几个版本。大多数情况是你需要一个开发版本,用来测试app和弄清它的质量,然后还需要一个生产版本。这些版本通常有不同的设置,例如不同的URL地址。更可能的是你可能需要一个免费版和收费版本。基于上述情况,你需要处理不同的版本:开发免费版,开发付费版本,生产免费版,生产付费版,而针对不同的版本不同的配置,这极大增加的管理难度。

Gradle有一些方便的方法来管理这些问题。我们很早之前谈过debug和release版本,现在我们谈到另外一个概念,不同的产品版本。构建版本和生产版本通常可以合并,构建版本和生产版本的合并版叫做构建变种。

这一章我们将学习构建版本,它能使得开发更有效率,并且学习如何使用它们。然后我们会讨论构建版本和生产版本的不同,以及如何将其合并。我们会探讨签名机制,如何针对不同的变种签名等。

在这一章,我们遵循如下规则:

  • Build types
  • Product flavors
  • Build variants
  • Signing configurations

构建版本

在Gradle的Android插件中,一个构建版本意味着定义一个app或者依赖库如何被构建。每个构建版本都要特殊的一面,比如是否需要debug,application id是什么,是否不需要的资源被删除等等。你可以定义一个构建版本通过buildTypes方法。例如:

android { 
      buildTypes { 
          release { 
              minifyEnabled false 
              proguardFiles getDefaultProguardFile 
                ('proguard-android.txt'), 'proguard-rules.pro' 
          } 
       } 
}

这个文件定义了该模块是release版本,然后定义了proguard的位置。该release版本不是唯一的构建版本,默认情况下,还有个debug版本。Android studio把它视为默认构建版本。

创建自己的构建版本

当默认的构建版本不够用的时候,创建版本也是很容易的一件事,创建构建版本你只需要在buildTypes写入自己的版本。如下所示:

android { 
       buildTypes { 
           staging { 
               applicationIdSuffix ".staging" 
               versionNameSuffix "-staging" 
               buildConfigField "String", 
                  "API_URL", "\"http://staging.example.com/api\"" 
            } 
       }
 }

我们定义了一个staging版本,该版本定义了一个新的application id,这让其与debug和release版本的applicationID不同。假设你使用了默认的配置,那么applicationID将会是这样的:

  • Debug: com.package
  • Release: com.package
  • Staging: com.package.staging

这意味着你可以在你的设备上安装staging版本和release版本。staging版本也有自己的版本号。buildConfigField定义了一个新的URL地址。你不必事事都去创建,所以最可能的方式是去继承已有的版本。

android { 
    buildTypes { 
        staging.initWith(buildTypes.debug) 
        staging { 
            applicationIdSuffix ".staging" 
            versionNameSuffix "-staging" 
            debuggable = false 
        } 
    }
}

initWith()方法创建了一个新的版本的同时,复制所有存在的构建版本,类似继承。我们也可以复写该存在版本的所有属性。

Source sets

当你创建了一个新的构建版本,Gradle也创建了新的source set。默认情况下,该文件夹不会自动为你创建,所有你需要手工创建。

 app
└── src
├── debug
│ ├── java 
    │ │ └── com.package 
    │ │
│ ├── res
│ │ └── layout
│         │ └── activity_main.xml│ └── AndroidManifest.xml├── main│ ├── java│ │ └── com.package│ ││ ├── res└── MainActivity.java└── Constants.java│ ││ ││ ││ └── AndroidManifest.xml├── staging│ ├── java│ │ └── com.package├── drawable└── layout└── activity_main.xml│ ││ ├── res│ │ └── layout│ │ └── activity_main.xml│ └── AndroidManifest.xml└── release ├── java │ └── com.package │ └── Constants.java └── AndroidManifest.xml 

注意:当你添加一个Java类的时候,你需要知道以下过程,当你添加了CustomLogic.java到staging版本,你可以添加相同的类到debug和release版本,但是不能添加到main版本。如果你添加了,会抛出异常。

当使用不同的source sets的时候,资源文件的处理需要特殊的方式。Drawables和layout文件将会复写在main中的重名文件,但是values文件下的资源不会。gradle将会把这些资源连同main里面的资源一起合并。

举个例子,当你在main中创建了一个srings.xml的时候:

<resources>
    <string name="app_name">TypesAndFlavors</string> 
    <string name="hello_world">Hello world!</string>
</resources>

当你在你的staing版本也添加了rings.xml:

<resources> 
    <string name="app_name">TypesAndFlavors STAGING</string>
</resources>

然后合并的strings.xml将会是这样的:

<resources> 
    <string name="app_name">TypesAndFlavors STAGING</string> 
    <string name="hello_world">Hello world!</string>
</resources>

当你创建一个新的构建版本而不是staging,最终的strings.xml将会是main目录下的strings.xml。
manifest也和value文件下的文件一样。如果你为你的构建版本创建了一个manifest文件,那么你不必要去拷贝在main文件下的manifest文件,你需要做的是添加标签。Android插件将会为你合并它们。
我们将在会之后的章节讲到合并的更多细节。

依赖包

每一个构建版本都有自己的依赖包,gradle自动为每一个构建的版本创建不同的依赖配置。如果你想为debug版本添加一个logging框架,你可以这么做:

dependencies {
     compile fileTree(dir: 'libs', include: ['*.jar']) 
     compile 'com.android.support:appcompat-v7:22.2.0' 
     debugCompile 'de.mindpipe.android:android-logging-log4j:1.0.3'
}

你可以结合不同的构建版本着不同的构建配置,就像这种方式,这让你的不同版本的不同依赖包成为可能。

product flavors


和构建版本不同,product flavors用来为一个app创建不同版本。典型的例子是,一个app有付费和免费版。product flavors极大简化了基于相同的代码构建不同版本的app。
如果你不确定你是否需要一个新的构建版本或者product flavors,你应该问你自己,你是否需要内部使用和外部使用的apk。如果你需要一个完全新的app去发布,和之前的版本完全隔离开,那么你需要product flavors。否则你只是需要构建版本。

创建product flavors

创建product flavors非常的容易。你可以在productFlavors中添加代码:

android { 
      productFlavors {
           red { 
                applicationId 'com.gradleforandroid.red' versionCode 3 
           } 
          blue { 
              applicationId 'com.gradleforandroid.blue' 
              minSdkVersion 14 versionCode 4 
          } 
     }
}

product flavors和构建版本的配置不同。因为product flavors有自己的ProductFlavor类,就像defaultConfig,这意味着你的所有productFlavors都分享一样的属性。

Source sets

就像构建版本一样,product Flavors也有自己的代码文件夹。创建一个特殊的版本就像创建一个文件夹那么简单。举个例子,当你有的生产版本的blue flavors有一个不同的app图标,该文件夹需要被叫做blueRelease。

多个flavors构建变体

在一些例子中,你可能需要创建一些product flavors的合并版本。举个例子,client A和client B可能都想要一个free和paid的版本,而他们又都是基于一样的代码,但是有不一样的颜色等。创建四个不同的flavors意味着有重复的配置。合并flavors最简单的做法可能是使用flavor dimensions,就像这样:

android { 
    flavorDimensions "color", "price" 
    productFlavors { 
        red { 
            flavorDimension "color" 
        } 
        blue { 
            flavorDimension "color" 
        } 
        free { 
          flavorDimension "price" 
        } 
        paid { 
          flavorDimension "price" 
        } 
    }
}

当你添加了flavor dimensions,你就需要为每个flavor添加flavorDimension,否则会提示错误。flavorDimensions定义了不同的dimensions,当然其顺序也很重要。当你合并二个不同的flavors时,他们可能有一样的配置和资源。例如上例:

  • blueFreeDebug and blueFreeRelease
  • bluePaidDebug and bluePaidRelease
  • redFreeDebug and redFreeRelease
  • redPaidDebug and redPaidRelease

构建变体


构建变体是构建版本和生产版本的结合体。当你创建了一个构建版本或者生产版本,同样的,新的变体也会被创建。举个例子,当你有debug和release版本,你创建了red和blue的生产版本,那么变体将会有四个:

你可以在Android studio的左下角找到它,或者通过VIEW|Tool Windows|Build Variants打开它。该视图列出了所有的变体,并且允许你去切换它们。改变他们将会影响到你按Run按钮。

如果你没有product flavors,那么变体只是简单的包含构建版本,就算你没有定义任何构建版本,Android studio也会默认为你创建debug版本的。

tasks

android插件回味每一个变体创建不同的配置。一个新的Android项目会有debug和release版本,所有你可以使用assembleDebug和assembleRelease,当然当你使用assemble命令,会二者都执行。当你添加了一个新的构建版本,新的task也会被创建。例如:

  • assembleBlue uses the blue flavor configuration and assembles both BlueRelease and BlueDebug.
  • assembleBlueDebug combines the flavor configuration with the build type configuration, and the flavor settings override the build type settings.

Source sets

构建变体也可以有自己的资源文件夹,举个例子,你可以有src/blueFreeDebug/java/。
资源文件和manifest的合并
在打包app之前,Android插件会合并main中的代码和构建的代码。当然,依赖项目也可以提供额外的资源,它们也会被合并。你可能需要额外的Android权限针对debug变体。举个例子,你不想在main中申明这个权限,因为这可能导致一些问题,所以你可以添加一个额外的mainfest文件在debug的文件夹中,申明额外的权限。
资源和mainfests的优先级是这样的:

如果一个资源在main中和在flavor中定义了,那么那个在flavor中的资源有更高的优先级。这样那个在flavor文件夹中的资源将会被打包到apk。而在依赖项目申明的资源总是拥有最低优先级。

创建构建变体

gradle让处理构建变体变得容易。

android { 
    buildTypes { 
        debug { 
            buildConfigField "String", "API_URL", 
              "\"http://test.example.com/api\"" 
        } 
        staging.initWith(android.buildTypes.debug) 
        staging { 
            buildConfigField "String", 
                "API_URL", "\"http://staging.example.com/api\""
           applicationIdSuffix ".staging" 
        }
     } 
    productFlavors { 
        red { 
            applicationId "com.gradleforandroid.red" 
            resValue "color", "flavor_color", "#ff0000" 
        } 
        blue { 
            applicationId "com.gradleforandroid.blue" 
            resValue "color", "flavor_color", "#0000ff" 
        } 
    }
}

在这个例子中,我们创建了4个变体,分别是blueDebug,blueStaging,redDebug,redStaging。每一个变体都有其不同的api url以及颜色。例如:


变体过滤器

忽略某个变体也是可行的。这样你可以加速你的构建当使用assemble的时候,这样你列出的tasks将不会执行那么你不需要的变体。你可以使用过滤器,在build.gradle中添加代码如下所示:

android.variantFilter {  variant ->
      if(variant.buildType.name.equals('release')) { 
          variant.getFlavors().each() { flavor -> 
              if (flavor.name.equals('blue')) { 
                  variant.setIgnore(true); 
              } 
          } 
      }
}

在这个例子中,我们检查下:

你可以看到blueFreeRelease和bluePaidRelease被排除在外,如果你运行gradlew tasks,你会发现所有的关于上述变体的tasks不再存在。

签名配置

在你发布你的应用之前,你需要为你的app私钥签名。如果你有付费版和免费版,你需要有不同的key去签名不同的变体。这就是配置签名的好处。配置签名可以这样定义:

android { 
    signingConfigs { 
        staging.initWith(signingConfigs.debug) 
        release { 
            storeFile file("release.keystore") 
            storePassword"secretpassword" 
            keyAlias "gradleforandroid" 
            keyPassword "secretpassword" 
        } 
    }
}

在这个例子中,我们创建了2个不同的签名配置。debug配置是as默认的,其使用了公共的keystore和password,所以没有必要为debug版本创建签名配置了。staging配置使用了initWith()方法,其会复制其他的签名配置。这意味着staging和debug的key是一样的。

release配置使用了storeFile,定义了key alias和密码。当然这不是一个好的选择,你需要在 Gradle properties文件中配置。
当你定义了签名配置后,你需要应用它们。构建版本都有一个属性叫做signingConfig,你可以这么干:

android { 
    buildTypes { 
        release { 
            signingConfig signingConfigs.release 
        } 
    }
}

上例使用了buildTypes,但是你可能需要对每个版本生成不同的验证,你可以这么定义:

 android { 
    productFlavors { 
        blue { 
            signingConfig signingConfigs.release 
        } 
    }
 }

当然,你在flavor中定义这些,最好会被重写,所以最好的做法是:

android { 
    buildTypes { 
        release { 
            productFlavors.red.signingConfig signingConfigs.red 
            productFlavors.blue.signingConfig signingConfigs.blue 
        } 
     }
}

总结

在这一章,我们讨论了构建版本和生产版本,以及如何结合它们。这将会是非常有用的工具,当你需要不同的url以及不同的keys,而你们有相同的代码和资源文件,但是有不同的类型以及版本,构建版本和生产版本将会让你的生活更美好。
我们也谈论了签名配置以及如何使用他们。
下一章,你将会学习到多模块构建,因为当你想把你的代码分成一个依赖包或者依赖项目的时候,或者你想把Android wear模块放在你的应用的时候,这将非常重要。

作者:gsg8709 发表于2017/9/22 13:50:03 原文链接
阅读:1 评论:0 查看评论

为微信开发填坑:微信网页支付的开发流程及填坑技巧

$
0
0

GitChat 作者:极笔北客
原文:为微信开发填坑:微信网页支付的开发流程及填坑技巧
关注微信公众号:「GitChat 技术杂谈」 一本正经的讲技术

【不要错过文末彩蛋】

小程序作为微信之父张小龙钦点,并多次公开为之宣传造势的产品,在微信之后是仅有的一次。正因为这种特殊的优待,在小程序上线后,据说内测资格一度从100w被炒到300w,先不论是真是假,单是张小龙团队和市场对小程序的期待,就足以引起我们的重视,做为一个开发人员,也非常有必要学习和了解小程序的开发原理及流程。如果你已经准备要做小程序开发,那么这篇文章就来的很及时。如果你的业务还不需要涉足小程序,你也可以通过本文对小程序的开发做一个基本的了解,以备不时之需。

本文会从小程序前端开发,小程序服务端开发及小程序的发布与审核三个方面来阐述小程序的开发流程。

一、小程序前端介绍及开发

小程序的开发涉及到前端开发和后端开发,前端指的是在手机上能看到的部分,主要负责页面的布局排版及展示,后端提供数据和业务处理能力,指的是我们写给前端调用的API接口。

注册账号

小程序的注册比较简单,首先,登录微信公众号平台:http://mp.weixin.qq.com ,点击右上角“立即注册”按钮。

enter image description here

选择小程序

enter image description here

注册小程序

enter image description here

在注册小程序时,这里输入的邮箱,一定要是未在腾讯平台未使用过的邮箱,否则会提示邮箱已经被使用。注意这里说的腾讯平台,比如你用来注册微信公众号的邮箱、用来注册企鹅号的邮箱,都是不能用的。

注册成功之后,需要进入邮箱激活。激活后,按照要求,选择申请类型,进行注册开通。

创建工程

为了支持小程序的开发, 微信官方研发了一个叫做微信开发者工具的东西,这个工具最初是为了协助微信公众号开发者做开发时用的,当微信小程序上线以后,微信开发者工具同步更新,也支持了小程序的开发。由于小程序中的页面及部分语法,完全是微信自己封装好的,同时小程序的编译发布,都只能在微信开发者工具中完成,所以,微信开发者工具成为了大部分小程序开发者使用的开发工具。跟其他开发工具相比,微信开发者工具的易用性还是比较差,所以,一部分人用其他的开发工具做开发,只用微信开发者工具编译和发布,虽然比较麻烦,但是效率提高不少,比价推荐的工具是国产的EgretWing。

微信开发者工具下载地址:

https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/devtools.html

点击蓝色字体“开发者工具”即可。

enter image description here

安装完微信开发者工具,第一次打开,会提示让扫描二维码,这只是一个开发授权,只要微信在小程序后台被绑定为开发者的微信,扫描都可以。扫描完成后登录开发工具。

登录成功之后,进入项目列表页面,如果之前打开过小程序,则会以列表显示。

enter image description here

点击“添加项目”,进入创建小程序页面。

enter image description here

这里的APPID,是小程序开发权限的认证,如果不填,选择“无APPID”也可以进行开发,但是无法正常发布小程序。APPID在小程序后台可以拿到,如图:

enter image description here

小程序的项目名称,可以根据自己的实际项目填写,支持中英文。

项目目录,是指开发目录,选择指向到要开发的小程序目录即可。点击确定,一个新的小程序项目就创建成功。

enter image description here

enter image description here

工程结构

新建的小程序项目如图:

enter image description here

上图中,区块1是菜单栏,关于小程序的操作菜单都在这里。

编辑:也是默认模式,在此模式下,可以对小程序源码进行编辑;

编译:在此模式下,可以编译调试小程序,小程序的日志输出会在日志区域打印出来;

项目:在此模式下,可以对已经开发完成的或者可以提测的小程序进行打包发布。

区块2是预览区域,小程序的页面展示,页面间的交互,都在这里,这块的小程序跟发布出去在手机上点开的小程序显示是完全一样的。

区块3是工程代码结构,展示出项目中所有的文件及文件间的关系。

区块4是代码区域,开发主要在这个区域进行编码。

每一个微信小程序,都会有三个公共入口文件:

app.json:配置文件,配置路由列表、程序信息等。

app.js:公共入口文件,小程序启动时的 Init 逻辑。

app.wxss :公共样式文件,公共样式用于每个视图 View 中。

同时,也会有pages这个文件夹,文件夹内就是所有的前端页面文件。

主要文件

小程序前端的文件有四类,js、json、wxml、wxss。

js:主要负责调用后端接口做数据交互,页面逻辑处理;

json:主要用来存储数据内容,一般用的较少;

wxml:相当于html,主要用来展示页面信息;

wxss:相当于css,跟css语法基本一致;

如图:

enter image description here

在小程序中,每一个页面需要创建一个文件夹,如上图中的list,该文件夹下需要创建以上四个文件,需要注意的是,这四个文件的文件名要和文件夹保持一致。

常用方法

微信小程序本身并没有创造出新的编程语言,其本质还是h5、css、js,是最基础的前端技术。所以,小程序的技术门槛较低,很多小程序都是一周时间开发并上线的。

但是微信对以上三种前端技术都做了一定的封装,用wxml来代替h5,其中的标签做了大量封装,如图:

enter image description here

同时,把css封装为wxss,这个几乎没什么变化。

再说js,封装了很多微信内部的js,在小程序中,这些被封装的js方法叫做组件。比较常用的有这些:

wx.request:用来做网络请求,小程序前端跟后端API交互,就用的是这个组件;

wx.navigateTo:保留当前页面,跳转到应用内的某个页面,使用wx.navigateBack可以返回到原页面;

wx.pageScrollTo:将页面滚动到目标位置;

wx.setNavigationBarTitle:动态设置当前页面的标题。

所有的组件可以在这里查看文档:

https://mp.weixin.qq.com/debug/wxadoc/dev/api/

二、小程序服务端介绍及开发

小程序前段负责内容的展示,如果我们开发的是纯静态页面,那么只需要掌握上面的就可以。但是,如果我们要做动态页面,也就是页面内容是跟数据库交互的,就需要服务端来提供数据的交互。在小程序中,服务端是以接口的方式实现的,结果以json数据格式返回。

服务接口介绍

服务端的接口开发跟一般的接口是一样的,可以用任何一种后端开发语言来实现,接口开发完成后,小程序前段通过组件wx.request调用接口,来实现跟服务端的交互,如图:

enter image description here

看到这个方法大家应该很眼熟,对,实际上wx.request就是jquery中的ajax,使用方法完全一样。在这个接口调用中,小程序前端调用后端接口,获取到了新闻列表,然后将结果集赋值给list的变量。

在小程序对应的前端页面news.wxml中,将list进行遍历展示,如图:

enter image description here

这样,我们就完成了一个小程序中的列表页面。其他的服务端交互都是类似的,小程序的开发工作,到这里其实已经结束,下面都是相关的配置。

安全证书的申请

前面说到,在小程序中服务端接口的开发跟一般的接口是一样的,一般的接口大部分都采用的http协议,但是,小程序要求必须是https安全协议,否则接口调用会失败,这是强制的。所以,我们的服务端必须安装安全证书,采用https协议对外发布接口。

网络上提供安全证书的服务商很多, 比如赛门铁克,有收费何免费的证书。如果是企业应用,建议去买一个企业级的安全证书,如果只是个人开发研究的话,这里给大家介绍一个比较靠谱的免费安全证书——阿里云。阿里云给个人用户提供不限时长的免费证书,注意一定是个人用户,企业用户是没有这个福利的。首先,以个人用户账户登陆阿里云,找到“CA证书服务”,如图:

enter image description here

进入证书服务页面后,点击“立即购买”,如图:

enter image description here

选择免费证书,立即购买:

enter image description here

在控制台中,找到“证书服务”,按照要求填写信息就能生成绑定指定域名的证书。

enter image description here

证书生成后,可以下载,在下载的证书文件中,有详细的说明文档,告诉你在不同的环境中如何快速安装配置安全证书,这里就不赘述。

三、小程序的发布与审核

提交与发布

小程序开发完成之后,就可以在微信开发者工具中进行提交,如图:

enter image description here

在项目模式下,点击上传,即可完成小程序代码的上传,上传完成后,需要登陆到小程序管理后台,需要设置相关项,如图:

enter image description here

首先,设置合法域名,也就是服务端接口的域名地址,这里注意不要填错。

其次,填写小程序基础信息:

enter image description here

这里需要注意的是服务类目的选择,一定要选择跟自己小程序对应的类目,一点类目选择不匹配,会审核不通过,再次提交再次审核,就需要3到5个工作日,很多人在这个地方一耗就是一个多月。

只要基础信息审核通过,就可以进性小程序发布审核,如图:

enter image description here

小程序发布审核通过后,就可以在微信中搜索到已经发布的小程序,至此,小程序的开发流程完成。

四、总结

小程序的开发从开发技能上讲,完全是前端开发,不涉及任何新的技能,基本上也没有什么开发难度,只要是掌握h5、css、js的开发人员,都可以胜任。比较麻烦的是小程序开发流程及开发完成后的各种资质的审核,微信官方的文档讲的不清楚,很多时候不知道自己的小程序属于那种类目,就得去试,错一次多的话会浪费一周,所以,这篇文章没有详细讲解小程序的开发部分,相关文章网上很多,而是着重讲解了小程序的开发流程及审核发布,希望能帮到正在或将要做小程序开发的朋友,有问题可以关注微信公众号“极笔北客”或微博“极笔北客”。

实录:《王冬强:小程序快速上手开发实战解析》


彩蛋

重磅 Chat 分享:

《高效学习,快速变现:不走弯路的五大学习策略》

分享人:
一名会在 B 站直播写代码,会玩杂耍球、弹 Ukulele、极限健身、跑步、写段子、画画、翻译、写作、演讲、培训的程序员。喜欢用编程实现自己的想法,在 Android 市场上赚过钱,有多次创业经历。擅长学习,习惯养成,时间管理。身体力行地影响他人做出积极的改变!目前就职于 ThoughtWorks,致力于传播快乐高效的编程理念。业余创立软件匠艺社区 CodingStyle.cn,组织超过30场技术活动。

Chat简介:
说到学习呀,真是头大哟:碎片化,没有较长的连续时间来学习难专注,捧起书,手机却在召唤:来呀,快活呀~ 反正有,大把时光~做不到,看了很多书,生活中却做不到然并卵,学了方法和工具,找不到使用场景效率低,学习速度跟不上知识产生的速度记不牢,学习速度赶不上遗忘速度在这个知识泛滥、跨界竞争的年代,学习能力才是核心竞争力。你想想,过去一周,有没有哪一件工作是不需要学习就能完成的?尽管如此重要,大部分人却没研究过学习这件事,以为上下班路上打开「得到」听本书,就是碎片时间终身学习者了。

我是程序员,咨询师,培训师,这几个角色都要求我必须学得又快又好。本场 Chat 将分析学习的「趋势,原则,策略」,帮你站在更高的视角看待学习,从「内容,动机,交互,收益,资源」五方面制定策略,解决学习痛点,助你成为高效学习者!

想要免费参与本场 Chat ?很简单,「GitChat技术杂谈」公众号后台回复「高效学习」

这里写图片描述

作者:GitChat 发表于2017/9/21 14:18:38 原文链接
阅读:44 评论:0 查看评论

iOS编译库文件时出现的问题

$
0
0

1. 警告:directory not found for option “xxxxxxxx” 文件路径未找到 **

  • 选择工程, 编译的 (targets)
  • 选择 Build Settings 菜单
  • 查找 Library Search Paths 和 Framework Search Paths, 删掉编译报warning的路径即OK
    详细步骤图文参考

2. missing required architecture i386 in file “xxxxxx/.a” 说明你的这个library文件是为Device (ARM架构)设计的,不支持Simulator (i386架构)

相关知识
参考stackoverfollow
arm架构问题手机的架构是为arm架构

  • armv6
    iPhone
    iPhone2
    iPhone3G第一代和第二代iPod Touch
  • armv7
    iPhone4
    iPhone4S
  • armv7s
    iPhone5
    iPhone5C
  • arm64
    iPhone5S

现在app store规定提交的应用必须包含arm64.
xcode里Architectures这个属性

这是指你想支持的指令集,比如:armv7,armv7s,或者可以用$(ARCHS_STANDARD_32_BIT)这样的参数
该编译选项指定了工程将被编译成支持哪些指令集,支持指令集是通过编译生成对应的二进制数据包实现的,如果支持的指令集数目有多个,就会编译出包含多个指令集代码的数据包,造成最终编译的包很大。
xcode工程里有Build Active Architecture Only这个属性

这个属性设置为yes,是为了debug的时候编译速度更快,它只编译当前的architecture版本。而设置为no时,会编译所有的版本。

编译出的版本是向下兼容的,比如你设置此值为yes,用iphone4编译出来的是armv7版本的,iphone5也可以运行,但是armv6的设备就不能运行。

所以,一般debug的时候可以选择设置为yes,release的时候要改为no,以适应不同设备

library not found for -lMobClickLibrary

在other link flag 里删除-l “MobClickLibrary”

我遇到的问题

微信demo下载下来运行出现 missing required architecture i386 in file “xxxxxx/libWeChatSDK.a”问题,用终端查看了一下这个库文件只支持 armv7,arm64


在终端里查看你的.a库是不是支持i386

  • 解决方法:
    找找看有没有支持i386的库文件(下载SDK,要下载的sdk里面的那个libWeChatSDK.a才有支持i386的,里面有两个文件夹 WeChatSDK_1.5WeChatSDK_1.5_OnlyIphone,选择WeChatSDK_1.5)WeChatSDK_1.5

有i386的libWeChatSDK.a

  • 使用真机测试

2.解决架构问头后在工程里出现了以下问题:Undefined symbols for architecture x86_64:”operator delete”, referenced from:+[WeChatApiUtil EncodeBase64:] in libWeChatSDK.a(WeChatApiUtil.o)
屏幕快照 2014-12-11 下午3.43.16.png

解决方法:
* 果断加上libc++.dylib

作者:gsg8709 发表于2017/9/22 14:09:54 原文链接
阅读:35 评论:0 查看评论

Android——ViewPager无限循环滑动

$
0
0

说到ViewPager的无限循环滚动,可1、能大家都在熟悉不过了吧,表现效果就是:向右滑动,滑动到最后一页,继续滑动进入第一页,向左滑动则相反,滑到第一页若继续滑动则进入最后一页。

至于如何实现这个效果有太多人讲过了,我搜集了一些,今天就给大家总结下思路吧。
-1、将PagerAdapter中的getCount()的返回值设为无限大,这样用户就永远不会滑动到最后一页,给人感觉就是无限滑动,这样做的弊端是会生成太多的页面实例。
-2、第二种方法用代码来解释吧:

 private final class MyPageChangeListener implements OnPageChangeListener {  

    private int currentPosition;  

    @Override  
    public void onPageScrollStateChanged(int state) {  
        if (state == ViewPager.SCROLL_STATE_IDLE) {  
            if (currentPosition == viewPager.getAdapter().getCount() - 1) {  
                viewPager.setCurrentItem(1, false);  
            }  
            else if (currentPosition == 0) {  
                viewPager.setCurrentItem(viewPager.getAdapter().getCount() - 2, false);  
            }  
        }  
    }  

    @Override  
    public void onPageScrolled(int scrolledPosition, float percent, int pixels) {  
        //empty  
    }  

    @Override  
    public void onPageSelected(int position) {  
        currentPosition = position;  
    }  

}  

这种做法看起来没有问题,但是要知道onPageScrollStateChanged的state状态有时会出现问题,比如用户一直拖着不放,然后松开,这时就会出现缺页的效果,显然这并不是我们想要的,还有个问题就是这种方法的目的其实是一直将Viewpager设定在某个页面上。
原文链接:真无限循环的ViewPager——解决两端滑动的平滑问题
-3、方法三同样看代码,这个缺陷跟大了,滑动不流畅,页首页尾切换会缺页
原文链接:Android 使用ViewPager实现无限无缝循环

@Override
  public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    if (dataSource.size() == 1) {
      return;
    }
    if (0 != positionOffset) return;

    if (position == INDEX) {
      return;
    }
    if (position > INDEX) {
      currentPage++;
    } else {
      currentPage--;
    }
    if (currentPage == -INDEX) {
      currentPage = dataSource.size() - INDEX;
    } else if (currentPage == dataSource.size()) {
      currentPage = START_INDEX;
    }
    indexChange();
  }

  private void indexChange() {
    if (currentPage == START_INDEX) {
      Glide.with(context).load(dataSource.get(dataSource.size() - 1)).into(views.get(0));
    } else {
      Glide.with(context).load(dataSource.get(currentPage - 1)).into(views.get(0));
    }
    Glide.with(context).load(dataSource.get(currentPage)).into(views.get(1));
    if (currentPage == dataSource.size() - 1) {
      Glide.with(context).load(dataSource.get(0)).into(views.get(2));
    } else {
      if (currentPage == 0 && dataSource.size() == 2) {
        Glide.with(context).load(dataSource.get(dataSource.size() - 1)).into(views.get(2));
      } else {
        Glide.with(context).load(dataSource.get(currentPage+1)).into(views.get(2));
      }
    }
    viewPager.setCurrentItem(1, false);
  }

-4、最后说下方法四,我觉得效果已经流畅很多了,但是还不是太完善,如果大家有很好的方法,请告之在下。
该方法是在列表的头尾加两个假数据,列表头添加最后一个数据,列表尾加第一个数据。然后切换到列表尾部的时候定位到列表的第二个也就是第一个真实数据,切换到列表头的时候定位到最后一个真实数据。请看代码:

  @Override
            public void onPageScrollStateChanged(int state) {
                if (state == ViewPager.SCROLL_STATE_IDLE) {
                    if (currentPosition == viewPager.getAdapter().getCount() - 1) {
                        viewPager.setCurrentItem(1, false);
                    } else if (currentPosition == 0) {
                        viewPager.setCurrentItem(viewPager.getAdapter().getCount() - 2, false);
                    }
                }
            }

本篇文章重在思路,完整代码就贴了。

作者:u012230055 发表于2017/9/21 18:21:19 原文链接
阅读:47 评论:0 查看评论

回炉再造,灵活的YMenuView2.0诞生

$
0
0

出处
炎之铠邮箱:yanzhikai_yjk@qq.com
博客地址:http://blog.csdn.net/totond
本文原创,转载请注明本出处!
本项目GitHub地址:https://github.com/totond/YMenuView
欢迎 Star or Fork!

前言

  之前把我项目用到的类似于PathView的菜单YMenuView抽离出来,分享了实现思路,但是只是实现了在右下角的几种菜单弹出收回的效果,限制太大,适用性不强。这次重温了一下设计模式之后把YMenuView回炉再造,重构代码,打造出一个可以让用户发挥自己自由想象,实现自己想法需求的灵活YMenuView2.0,还加了3个自定义YMenu的例子,先来看看看效果:


(前面几个是之前YMenuView1.x的效果,后面是新加的)

介绍

  YMenuView是包含着一个MenuButton和若干个OptionButton的RelativeLayout,具体实现原理前一篇已经有介绍了,所以这篇文章主要说明YMenuView2.0的改变(使用方法的话可以直接看Github。YMenuView2.0重构代码之后,最大的改变就是把YMenuView的大部分逻辑抽离出来,留下4个主要的抽象方法,可以让用户自定义,这4个分别是决定:MenuButton的位置、OptionButton的位置、OptionButton的显示动画和OptionButton的消失动画。

重构过程

抽象类YMenu

  YMenuView2.0的主角不再是YMenuView类,现在的它只是一个小弟(子类),真正的大哥现在叫YMenu(父类)
  最近正在学习Animation的源码,得知Animation是一个抽象类,然后其他具体的动画时通过继承它,然后重写applyTransformation()方法来实现具体的动画需求,这是模板方法模式的应用(其实Activity也这样),所以YMenuView也参考这种模式,把一些次要的逻辑抽象出来,放都在抽象父类YMenu里面,开放出4个抽象方法给用户可以通过继承来自定义YMenu:

    /**
     * 设置MenuButton的位置,重写该方法进行自定义设置
     *
     * @param menuButton 传入传入MenuButton,此时它的宽高位置属性还未设置,需要在此方法设置。
     */
    public abstract void setMenuPosition(View menuButton);

    /**
     * 设置OptionButton的位置,重写该方法进行自定义设置
     *
     * @param optionButton 传入OptionButton,此时它的宽高位置属性还未设置,需要在此方法设置。
     * @param menuButton   传入MenuButton,此时它已经初始化完毕,可以利用。
     * @param index        传入的是该OptionButton的索引,用于区分不同OptionButton。
     */
    public abstract void setOptionPosition(OptionButton optionButton, View menuButton, int index);

    /**
     * 设置OptionButton的显示动画,重写该方法进行自定义设置
     *
     * @param optionButton 传入了该动画所属的OptionButton,此时它的宽高位置属性已初始化完毕,可以利用。
     * @param index        传入的是该OptionButton的索引,用于区分不同OptionButton。
     * @return             返回的是创建好的动画                    
     */
    public abstract Animation createOptionShowAnimation(OptionButton optionButton, int index);


    /**
     * 设置OptionButton的消失动画,重写该方法进行自定义设置
     *
     * @param optionButton 传入了该动画所属的OptionButton,此时它的宽高位置属性已初始化完毕,可以利用。
     * @param index        传入的是该OptionButton的索引,用于区分不同OptionButton。
     * @return             返回的是创建好的动画
     */
    public abstract Animation createOptionDisappearAnimation(OptionButton optionButton, int index);

  YMenu有4个具体实现类(效果都在上面的gif图里展现过了):

抽象方法的实现

  这4个方法抽象出来之后,由子类实现功能,下面介绍如何实现(以下的4个方法介绍的代码是在YMenuView里面的):

1.setMenuPosition()方法

  此方法的实现决定了MenuButton的位置,MenuButton就是上面效果图那个齿轮,点击之后会有选项出现消失的动作。在上一篇YMenuView源码分析里面就已经写到,它的位置是用Relative的LayoutParams来设置的(和上一篇有点不同就是因为是从抽象父类获取属性,所以要用一些get方法,而且我还重命名了一些属性,具体可以在Github的使用介绍属性表查看):

    @Override
    public void setOptionPosition(OptionButton optionButton, View menuButton, int index){

        LayoutParams layoutParams = new LayoutParams(getYMenuButtonWidth(), getYMenuButtonHeight());
        layoutParams.setMarginEnd(getYMenuToParentXMargin());
        layoutParams.bottomMargin = getYMenuToParentYMargin();
        layoutParams.addRule(ALIGN_PARENT_RIGHT);
        layoutParams.addRule(ALIGN_PARENT_BOTTOM);

        menuButton.setLayoutParams(layoutParams);
    }

  但是这样使用LayoutParams的话理解起来很不直观(其实熟悉RelativeLayout的还好),如果让用户自定义的话会有点麻烦,所以我把这个过程参考建造者模式封装成了一个类MenuPositionBuilder,通过它来实现这个位置的设置过程。

    @Override
    public void setMenuPosition(View menuButton){
        new MenuPositionBuilder(menuButton)
                //设置宽高
                .setWidthAndHeight(getYMenuButtonWidth(), getYMenuButtonHeight())
                //设置参考方向
                .setMarginOrientation(PositionBuilder.MARGIN_RIGHT,PositionBuilder.MARGIN_BOTTOM)
                //设置是否在XY方向处于中心
                .setIsXYCenter(false,false)
                //设置XY方向的参考,如果设置了MARGIN_LEFT和MARGIN_TOP,那么XMargin和YMargin就是与参照物左边界和上边界的距离
                .setXYMargin(getYMenuToParentXMargin(),getYMenuToParentYMargin())
                //最后确认时候调用
                .finish();
    }

  这样的话感觉就清晰一些了,当然有人如果熟悉了RelativeLayout参数的话,可能会觉得比原来复杂了,也可以用上面的写法,这两者是等价的。至于MenuPositionBuilder的实现过程,运用的建造者模式,比较简单,由于篇幅原因这里就不写了,有兴趣的话可以直接到项目Github地址上查看源码。

2.setOptionPosition()方法

  此方法的实现决定了那些选项OptionButton的位置,这里是YMenuView的实现:

    @Override
    public void setOptionPosition(OptionButton optionButton, View menuButton, int index){
        //设置动画模式和时长
        optionButton.setSD_Animation(getOptionSD_AnimationMode());
        optionButton.setDuration(getOptionSD_AnimationDuration());

        //计算OptionButton的位置
        int position = index % getOptionColumns();

        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
                getYOptionButtonWidth()
                , getYOptionButtonHeight());

        layoutParams.rightMargin = getYOptionToParentXMargin()
                + getYOptionXMargin() * position
                + getYOptionButtonWidth() * position;

        layoutParams.bottomMargin = getYOptionToParentYMargin()
                + (getYOptionButtonHeight() + getYOptionYMargin())
                * (index / getOptionColumns());
        layoutParams.addRule(ALIGN_PARENT_BOTTOM);
        layoutParams.addRule(ALIGN_PARENT_RIGHT);

        optionButton.setLayoutParams(layoutParams);
    }

  和MenuButton一样,这个方法也有一个专属的OptionPositionBuilder,下面使用它来设置:

    @Override
    public void setOptionPosition(OptionButton optionButton, View menuButton, int index){
        //设置动画模式和时长
        optionButton.setSD_Animation(getOptionSD_AnimationMode());
        optionButton.setDuration(getOptionSD_AnimationDuration());

        //计算OptionButton的位置
        int position = index % getOptionColumns();

        new OptionPositionBuilder(optionButton,menuButton)
                //设置宽高
                .setWidthAndHeight(getYOptionButtonWidth(), getYOptionButtonHeight())
                //设置在XY方向是否以MenuButton作为参照物
                .isAlignMenuButton(false,false)
                //设置参考方向
                .setMarginOrientation(PositionBuilder.MARGIN_RIGHT,PositionBuilder.MARGIN_BOTTOM)
                //设置XY方向的距离,如果设置了MARGIN_LEFT和MARGIN_TOP,那么XMargin和YMargin就是与参照物左边界和上边界的距离
                .setXYMargin(
                        getYOptionToParentXMargin()
                                + getYOptionXMargin() * position
                                + getYOptionButtonWidth() * position,
                        getYOptionToParentYMargin()
                                + (getYOptionButtonHeight() + getYOptionYMargin())
                                * (index / getOptionColumns()))
                //最后确认时候调用
                .finish();
    }

  这里YMenuView的OptionButton位置具体的实现逻辑上一篇介绍YMenuView1.x的时候已经说过了,这里就不详细说了。

3.createOptionShowAnimation()方法和createOptionDisappearAnimation()方法

  这两个方法用来决定OptionButton的出现和消失的动画,原本在YMenuView1.x版本是在OptionButton内部实现,但是为了我们可以通过继承YMenu来实现一个自定义的效果,所以就通过一个回调设计把这两个方法的具体实现放到YMenu里抽象出来了。
  所以,这个方法就是给出一个索引为index的OptionButton的参数,给设置动画作为参考值,然后把设置好的动画返回。

    @Override
    public Animation createOptionShowAnimation(OptionButton optionButton, int index){
        AnimationSet animationSet = new AnimationSet(true);
        AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);
        alphaAnimation.setDuration(getOptionSD_AnimationDuration());
        TranslateAnimation translateAnimation = new TranslateAnimation(0,0,0,0);
        switch (optionButton.getmSD_Animation()){
            //从MenuButton的左边移入
            case FROM_BUTTON_LEFT:
                translateAnimation= new TranslateAnimation(getYMenuButton().getX() - optionButton.getRight(),0
                        ,0,0);
                translateAnimation.setDuration(getOptionSD_AnimationDuration());
                break;
            case FROM_RIGHT:
                //从右边缘移入
                translateAnimation= new TranslateAnimation((getWidth() - optionButton.getX()),0,0,0);
                translateAnimation.setDuration(getOptionSD_AnimationDuration());
//                showAnimation.setInterpolator(new OvershootInterpolator(1.3f));
                break;
            case FROM_BUTTON_TOP:
                //从MenuButton的上边缘移入
                translateAnimation= new TranslateAnimation(0,0,
                        getYMenuButton().getY() - optionButton.getBottom(),0);
                translateAnimation.setDuration(getOptionSD_AnimationDuration());
                break;
            case FROM_BOTTOM:
                //从下边缘移入
                translateAnimation = new TranslateAnimation(0,0,getHeight() - optionButton.getY(),0);
                translateAnimation.setDuration(getOptionSD_AnimationDuration());
        }

        animationSet.addAnimation(translateAnimation);
        animationSet.addAnimation(alphaAnimation);
        return animationSet;
    }

  这里的逻辑其实和之前的没有变化,也是要注意Animation里面的坐标原点是View的左上角就行了,这里只列出ShowAnimation的实现,至于DisappearAnimation的话就是和这个相反而已,篇幅原因就不列出来了。

4.抽象方法的执行顺序

  这4个抽象方法是有执行顺序的,所以后面的方法能用到前面设置好的对象参数:

  这些顺序的具体细节就是:
- 先初始化MenuButton,让MenuButton经历Measure和Layout过程之后,再初始化OptionButton,这样OptionButton就能以MenuButton的位置信息做为参考。
- 跟着每一个OptionButton经历Measure和Layout过程之后,它的ShowAnimation和DisappearAnimation又能以它的宽高信息进行初始化。
- 这样根据不同时期放出了不同抽象方法,让用户可以通过实现这4个方法来实现自定义的YMenu。

Circle8YMenu的实现


  这是一个OptionButton围绕着MenuButton的布局,Option最大数量为8个,MenuButton的位置位于ViewGroup正中间,如果想改变的话可以继承Circle8YMenu,单单重写setMenuPosition()方法就可以了。

    //8个Option位置的x、y乘积因子
    private float[] xyTimes = {0,0.707f,1,0.707f,0,-0.707f,-1,-0.707f};

    //设置MenuButton的位置,这里设置成位于ViewGroup中心
    @Override
    public void setMenuPosition(View menuButton) {
        new MenuPositionBuilder(menuButton)
                .setWidthAndHeight(getYMenuButtonWidth(),getYMenuButtonHeight())
                .setMarginOrientation(PositionBuilder.MARGIN_RIGHT,PositionBuilder.MARGIN_BOTTOM)
                .setIsXYCenter(true,true)
                .setXYMargin(getYMenuToParentXMargin(),getYMenuToParentYMargin())
                .finish();
    }

    //设置OptionButton的位置,这里是设置成圆形围绕着MenuButton
    @Override
    public void setOptionPosition(OptionButton optionButton, View menuButton, int index) {
        if (index >= 8){
            try {
                throw new Exception("Circle8YMenuView的OptionPosition最大数量为8,超过将会发生错误");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        int centerX = menuButton.getLeft() + menuButton.getWidth()/2;
        int centerY = menuButton.getTop() + menuButton.getHeight()/2;
        int halfOptionWidth = getYOptionButtonWidth()/2;
        int halfOptionHeight = getYOptionButtonHeight()/2;
        //利用乘积因子来决定不同位置
        float x = xyTimes[index % 8];
        float y = xyTimes[(index + 6) % 8];

        OptionPositionBuilder OptionPositionBuilder = new OptionPositionBuilder(optionButton,menuButton);
        OptionPositionBuilder
                .isAlignMenuButton(false,false)
                .setWidthAndHeight(getYOptionButtonWidth(), getYOptionButtonHeight())
                .setMarginOrientation(PositionBuilder.MARGIN_LEFT,PositionBuilder.MARGIN_TOP)
                //计算OptionButton的位置
                .setXYMargin(
                        (int)(centerX + x * getYOptionXMargin() - halfOptionWidth)
                        ,(int)(centerY + y * getYOptionXMargin() - halfOptionHeight)
                )
                .finish();
    }

  这里设置了MenuButton和OptionButton的位置。MenuButton的位置没什么好说的,直接居中,而关于OptionButton的位置这里用到了乘积因子,我觉得这是一种比较方便的算法,在这里解释一下:把MenuButton的中心看作参考点,以optionXMargin为半径,OptionButton中心到参考点(这些在XML定义的属性到了这里怎么用就看自己了,这里就用optionXMargin),然后x,y乘以optionXMargin就是OptionButton到参考点的x,y方向距离。

以这个0号OptionButton为例,它的x=0,y=-1,然后是以ViewGroup的左和上边缘为参考,所以它就处于MenuButton的正上方相隔optionXMargin的位置。

  然后就是动画的实现:

    //设置OptionButton的显示动画
    @Override
    public Animation createOptionShowAnimation(OptionButton optionButton, int index) {
        AnimationSet animationSet = new AnimationSet(true);
        TranslateAnimation translateAnimation= new TranslateAnimation(
                getYMenuButton().getX() - optionButton.getX()
                ,0
                ,getYMenuButton().getY() - optionButton.getY()
                ,0);
        translateAnimation.setDuration(getOptionSD_AnimationDuration());
        AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);
        alphaAnimation.setDuration(getOptionSD_AnimationDuration());
        animationSet.addAnimation(alphaAnimation);
        animationSet.addAnimation(translateAnimation);
        //为不同的Option设置延时
        if (index % 2 == 1) {
            animationSet.setStartOffset(getOptionSD_AnimationDuration()/2);
        }
        return animationSet;
    }

    //设置OptionButton的消失动画
    @Override
    public Animation createOptionDisappearAnimation(OptionButton optionButton, int index) {
        AnimationSet animationSet = new AnimationSet(true);
        TranslateAnimation translateAnimation= new TranslateAnimation(
                0
                ,getYMenuButton().getX() - optionButton.getX()
                ,0
                ,getYMenuButton().getY() - optionButton.getY()
        );
        translateAnimation.setDuration(getOptionSD_AnimationDuration());
        AlphaAnimation alphaAnimation = new AlphaAnimation(1,0);
        alphaAnimation.setDuration(getOptionSD_AnimationDuration());
        animationSet.addAnimation(translateAnimation);
        animationSet.addAnimation(alphaAnimation);
        //为不同的Option设置延时
        if (index % 2 == 0) {
            animationSet.setStartOffset(getOptionSD_AnimationDuration()/2);
        }
        return animationSet;
    }

  动画都是选项从MenuButton里面冒出来和滚回去,亮点就是根据OptionButton的index设置了不同的延时,让整个效果看起来炫酷一点。

重写了这4个方法之后,就能够创造出这个Circle8YMenu了,如果想在这基础上改东西,直接重写这4个方法中要改变的方法就行了,有点方便。

TreeYMenu的实现


  这是一个OptionButton分布成分叉树的布局,Option最大数量为9个,MenuButton的位置位于ViewGroup中下方。

    //9个Option位置的x、y乘积因子
    private static final float[] xTimes = {-1,1,0,-2,-2,2,2,-1,1};
    private static final float[] yTimes = {-1,-1,-2,0,-2,0,-2,-3,-3};

    //设置MenuButton的位置,这是设置在屏幕中下方
    @Override
    public void setMenuPosition(View menuButton) {
        new MenuPositionBuilder(menuButton)
                //设置宽高
                .setWidthAndHeight(getYMenuButtonWidth(), getYMenuButtonHeight())
                //设置参考方向
                .setMarginOrientation(PositionBuilder.MARGIN_RIGHT,PositionBuilder.MARGIN_BOTTOM)
                //设置是否在XY方向处于中心
                .setIsXYCenter(true,false)
                //设置XY方向距离
                .setXYMargin(getYMenuToParentXMargin(),getYMenuToParentYMargin())
                .finish();
    }

    //设置OptionButton的位置,这里是把9个Option设置为树状布局
    @Override
    public void setOptionPosition(OptionButton optionButton, View menuButton, int index) {
        if (index > 8){
            try {
                throw new Exception("TreeYMenuView的OptionPosition最大数量为9,超过将会发生错误");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        int centerX = menuButton.getLeft() + menuButton.getWidth()/2;
        int centerY = menuButton.getTop() + menuButton.getHeight()/2;
        int halfOptionWidth = getYOptionButtonWidth()/2;
        int halfOptionHeight = getYOptionButtonHeight()/2;

        //利用乘积因子来决定不同位置
        float x = xTimes[index];
        float y = yTimes[index];
        OptionPositionBuilder OptionPositionBuilder = new OptionPositionBuilder(optionButton,menuButton);
        OptionPositionBuilder
                .isAlignMenuButton(false,false)
                .setWidthAndHeight(getYOptionButtonWidth(), getYOptionButtonHeight())
                .setMarginOrientation(PositionBuilder.MARGIN_LEFT,PositionBuilder.MARGIN_TOP)
                .setXYMargin(
                        (int)(centerX + x * getYOptionXMargin() - halfOptionWidth)
                        ,(int)(centerY + y * getYOptionYMargin() - halfOptionHeight)
                )
                .finish();
    }

  这里OptionButton的设置和Circle8YMenu的差不多,就是乘积因子改变了,摆成了一个树状的布局。动画的展开也是像树一样分叉:

    //设置OptionButton的显示动画,这里是为前三个先从MenuButton冒出,后面的分别从这三个冒出
    @Override
    public Animation createOptionShowAnimation(OptionButton optionButton, int index) {
        float fromX,fromY;
        AnimationSet animationSet = new AnimationSet(true);
        if (index < 3){
            fromX = getYMenuButton().getX() - optionButton.getX();
            fromY = getYMenuButton().getY() - optionButton.getY();
        }else {
            int oldIndex = (index - 3) / 2;
            fromX = getOptionButtonList().get(oldIndex).getX() - optionButton.getX();
            fromY = getOptionButtonList().get(oldIndex).getY() - optionButton.getY();
            //设置冒出动画延时
            animationSet.setStartOffset(getOptionSD_AnimationDuration());
        }

        TranslateAnimation translateAnimation= new TranslateAnimation(
                fromX
                ,0
                ,fromY
                ,0);
        translateAnimation.setDuration(getOptionSD_AnimationDuration());

        AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);
        alphaAnimation.setDuration(getOptionSD_AnimationDuration());

        animationSet.addAnimation(alphaAnimation);
        animationSet.addAnimation(translateAnimation);
        animationSet.setInterpolator(new LinearInterpolator());
        return animationSet;
    }

    //设置OptionButton的消失动画,这里设置的是直接从当前位置移动到MenuButton位置消失
    @Override
    public Animation createOptionDisappearAnimation(OptionButton optionButton, int index) {

        AnimationSet animationSet = new AnimationSet(true);
        TranslateAnimation translateAnimation= new TranslateAnimation(
                0
                ,getYMenuButton().getX() - optionButton.getX()
                ,0
                ,getYMenuButton().getY() - optionButton.getY()
        );
        translateAnimation.setDuration(getOptionSD_AnimationDuration());
        AlphaAnimation alphaAnimation = new AlphaAnimation(1,0);
        alphaAnimation.setDuration(getOptionSD_AnimationDuration());

        animationSet.addAnimation(translateAnimation);
        animationSet.addAnimation(alphaAnimation);
        //设置动画延时
        animationSet.setStartOffset(60*(getOptionPositionCount() - index));
        return animationSet;
    }

  这里OptionButton前三个是从MenuButton的位置冒出,其他的6个则是分别从这三个的位置冒出,利用延时实现了先后效果。而消失动画则是给每个OptionButton都加不同的延时,实现逐个回收的效果。

SquareYMenu的实现


  这是一个OptionButton和MenuButton组成正方形的布局,Option最大数量为8个,MenuButton为位置依靠右下。这里就不贴出源码了,直接给出乘积数组,因为其他代码思路和前面的差不多,想看具体代码的可以看项目Github地址。

    //8个Option位置的x、y乘积因子
    private static final int[] xTimes = {-1,-1,0,-2,-2,-1,-2,0};
    private static final int[] yTimes = {-1,0,-1,-2,-1,-2,0,-2};

Ban的补充

  YMenuView1.x就有有Ban功能,能把一些位置设为不放置OptionButton,那么YMenuView2.0也支持这个,例如:

        //对Circle8YMenu
        mYMenu.setBanArray(0,2,4,6);

        //对TreeYMenu
        mYMenu.setBanArray(2,8,7);

        //对SquareYMenu
        mYMenu.setBanArray(3,4,7,5,6);


  但是动画延时还是会算上不被填充的位置,这暂时无法避免,所以想要更好的体验效果的话就继承YMenu重写方法吧。

总结

  这次重构过程主要使用了模板方法模式把一些次要的逻辑封装起来,主要的类结构从一个YMenuView扩展为一个父类YMenu和4个子类。在这个过程中也学到了不少东西,感觉自己在这方面还是有很多不足,后面会陆续更新改进的。但是最后也把自己的想法实现了。在此记录并分享,如有意见和建议,敬请提出。如果喜欢YMenuView的话,也可以上Github点个Star ^_^!

作者:totond 发表于2017/9/22 9:16:21 原文链接
阅读:102 评论:0 查看评论

kotlin集合操作符——总数操作符

$
0
0

Kotlin学习笔记系列:http://blog.csdn.net/column/details/16696.html

关于集合的操作符,直接引用书上的内容,基本上总结的很好了。

any

如果至少有一个元素符合给出的判断条件,则返回true。

val list = listOf(1, 2, 3, 4, 5, 6)
assertTrue(list.any { it % 2 == 0 })
assertFalse(list.any { it > 10 })

all

如果全部的元素符合给出的判断条件,则返回true。

assertTrue(list.all { it < 10 })
assertFalse(list.all { it % 2 == 0 })

count

返回符合给出判断条件的元素总数。

assertEquals(3, list.count { it % 2 == 0 })

fold

在一个初始值的基础上从第一项到最后一项通过一个函数累计所有的元素。

assertEquals(25, list.fold(4) { total, next -> total + next })

foldRight

fold一样,但是顺序是从最后一项到第一项。

assertEquals(25, list.foldRight(4) { total, next -> total + next })

forEach

遍历所有元素,并执行给定的操作。

list.forEach { println(it) }

forEachIndexed

forEach,但是我们同时可以得到元素的index。

list.forEachIndexed { index, value
		-> println("position $index contains a $value") }

max

返回最大的一项,如果没有则返回null。

assertEquals(6, list.max())

maxBy

根据给定的函数返回最大的一项,如果没有则返回null。

// The element whose negative is greater
assertEquals(1, list.maxBy { -it })

min

返回最小的一项,如果没有则返回null。

assertEquals(1, list.min())

minBy

根据给定的函数返回最小的一项,如果没有则返回null。

// The element whose negative is smaller
assertEquals(6, list.minBy { -it })

none

如果没有任何元素与给定的函数匹配,则返回true。

// No elements are divisible by 7
assertTrue(list.none { it % 7 == 0 })

reduce

fold一样,但是没有一个初始值。通过一个函数从第一项到最后一项进行累计。

assertEquals(21, list.reduce { total, next -> total + next })

reduceRight

reduce一样,但是顺序是从最后一项到第一项。

assertEquals(21, list.reduceRight { total, next -> total + next })

sumBy

返回所有每一项通过函数转换之后的数据的总和。

assertEquals(3, list.sumBy { it % 2 })
作者:chzphoenix 发表于2017/9/22 17:43:34 原文链接
阅读:2 评论:0 查看评论

Android——SeekBar动态显示进度

$
0
0

今天给大家分享一下小例子,就是SeekBar在移动时,当前进度也一起移动,具体看图啦。

其实原理很简单,就是在Seekbar在移动时,改变当前时间的位置。
代码如下:

public class MainActivity extends RxAppCompatActivity {
    private TextView seekCurTime, curTime, totalTime;
    private SeekBar seekBar;
    //移动步长
    private float moveStep;
    private int screenWidth;
    private static final int TOTALTIME = 300;//5分钟,单位秒
    private int curtime;//单位秒

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        initView();
        initData();
    }

    private void initView() {
        seekCurTime = (TextView) findViewById(R.id.curSeekTime);
        curTime = (TextView) findViewById(R.id.curTime);
        totalTime = (TextView) findViewById(R.id.totalTime);

        seekBar = (SeekBar) findViewById(R.id.seekbar);
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
                curtime = progress * TOTALTIME / 100;
                curTime.setText(Utils.getAudioTime(curtime));
                seekCurTime.setText(Utils.getAudioTime(curtime));
                setSeekCurTimeLocation(progress);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });

    }

    private void initData() {
        curTime.setText(Utils.getAudioTime(0));
        totalTime.setText(Utils.getAudioTime(TOTALTIME));
        seekBar.setMax(100);

        screenWidth = getWindowManager().getDefaultDisplay().getWidth() - 60;
        moveStep = ((float) screenWidth / 100) * 1.0f;

        Observable.


                .interval(1, TimeUnit.SECONDS)
                .compose(this.<Long>bindToLifecycle())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Long>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(Long aLong) {
                        curtime++;
                        int progress = (int) ((float) curtime / TOTALTIME * 100);
                        seekBar.setProgress(progress);
                        curTime.setText(Utils.getAudioTime(curtime));
                        seekCurTime.setText(Utils.getAudioTime(curtime));
                        setSeekCurTimeLocation(progress);
                    }

                });
    }

    private void setSeekCurTimeLocation(int progress) {
        LinearLayout.LayoutParams layoutParams = (LinearLayout
                .LayoutParams) seekCurTime.getLayoutParams();
        int marginStart = (int) (progress * moveStep - seekCurTime.getWidth() / 2);
        if (marginStart <= seekBar.getWidth() -seekCurTime.getWidth()) {
            layoutParams.setMarginStart(marginStart);
        }
        seekCurTime.setLayoutParams(layoutParams);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

其他实现方式:Android使用SeekBar时动态显示进度且随SeekBar一起移动
layout()方法我在使用时无效,所以换成修改LayoutParams了。

作者:u012230055 发表于2017/9/22 17:47:13 原文链接
阅读:0 评论:0 查看评论

kotlin集合操作符——映射操作符

$
0
0

Kotlin学习笔记系列:http://blog.csdn.net/column/details/16696.html

关于集合的操作符,直接引用书上的内容,基本上总结的很好了。

val list = listOf(1, 2, 3, 4, 5, 6)

flatMap

遍历所有的元素,为每一个创建一个集合,最后把所有的集合放在一个集合中。

assertEquals(listOf(122334455667), list.flatMap { listOf(it, it + 1) })

groupBy

返回一个根据给定函数分组后的map。

assertEquals(mapOf("odd" to listOf(1, 3, 5), "even" to listOf(2, 4, 6)), list.groupBy { if (it % 2 == 0) "even" else "odd" })

map

返回一个每一个元素根据给定的函数转换所组成的List。

assertEquals(listOf(2, 4, 6, 8, 10, 12), list.map { it * 2 })

mapIndexed

返回一个每一个元素根据给定的包含元素index的函数转换所组成的List。

assertEquals(listOf (0, 2, 6, 12, 20, 30), list.mapIndexed { index, it -> index * it })

mapNotNull

返回一个每一个非null元素根据给定的函数转换所组成的List。

assertEquals(listOf(2, 4, 6, 8), listWithNull.mapNotNull { it * 2 })

作者:chzphoenix 发表于2017/9/22 17:50:16 原文链接
阅读:1 评论:0 查看评论

kotlin集合操作符——元素操作符

$
0
0

Kotlin学习笔记系列:http://blog.csdn.net/column/details/16696.html

关于集合的操作符,直接引用书上的内容,基本上总结的很好了。


val list = listOf(1, 2, 3, 4, 5, 6)

contains

如果指定元素可以在集合中找到,则返回true。

assertTrue(list.contains(2))

elementAt

返回给定index对应的元素,如果index数组越界则会抛出IndexOutOfBoundsException

assertEquals(2, list.elementAt(1))

elementAtOrElse

返回给定index对应的元素,如果index数组越界则会根据给定函数返回默认值。

assertEquals(20, list.elementAtOrElse(10, { 2 * it }))

elementAtOrNull

返回给定index对应的元素,如果index数组越界则会返回null。

assertNull(list.elementAtOrNull(10))

first

返回符合给定函数条件的第一个元素。

assertEquals(2, list.first { it % 2 == 0 })

firstOrNull

返回符合给定函数条件的第一个元素,如果没有符合则返回null。

assertNull(list.firstOrNull { it % 7 == 0 })

indexOf

返回指定元素的第一个index,如果不存在,则返回-1

assertEquals(3, list.indexOf(4))

indexOfFirst

返回第一个符合给定函数条件的元素的index,如果没有符合则返回-1

assertEquals(1, list.indexOfFirst { it % 2 == 0 })

indexOfLast

返回最后一个符合给定函数条件的元素的index,如果没有符合则返回-1

assertEquals(5, list.indexOfLast { it % 2 == 0 })

last

返回符合给定函数条件的最后一个元素。

assertEquals(6, list.last { it % 2 == 0 })

lastIndexOf

返回指定元素的最后一个index,如果不存在,则返回-1

lastOrNull

返回符合给定函数条件的最后一个元素,如果没有符合则返回null。

val list = listOf(1, 2, 3, 4, 5, 6)
assertNull(list.lastOrNull { it % 7 == 0 })

single

返回符合给定函数的单个元素,如果没有符合或者超过一个,则抛出异常。

assertEquals(5, list.single { it % 5 == 0 })

singleOrNull

返回符合给定函数的单个元素,如果没有符合或者超过一个,则返回null。

assertNull(list.singleOrNull { it % 7 == 0 })

作者:chzphoenix 发表于2017/9/22 17:52:19 原文链接
阅读:0 评论:0 查看评论

kotlin集合操作符——生产操作符

$
0
0

Kotlin学习笔记系列:http://blog.csdn.net/column/details/16696.html

关于集合的操作符,直接引用书上的内容,基本上总结的很好了。

merge

把两个集合合并成一个新的,相同index的元素通过给定的函数进行合并成新的元素作为新的集合的一个元素,返回这个新的集合。新的集合的大小由最小的那个集合大小决定。

val list = listOf(1, 2, 3, 4, 5, 6)
val listRepeated = listOf(2, 2, 3, 4, 5, 5, 6)
assertEquals(listOf(3, 4, 6, 8, 10, 11), list.merge(listRepeated) { it1, it2 -> it1 + it2 })

partition

把一个给定的集合分割成两个,第一个集合是由原集合每一项元素匹配给定函数条件返回true的元素组成,第二个集合是由原集合每一项元素匹配给定函数条件返回false的元素组成。

assertEquals(
	Pair(listOf(2, 4, 6), listOf(1, 3, 5)), 
	list.partition { it % 2 == 0 }
)

plus

返回一个包含原集合和给定集合中所有元素的集合,因为函数的名字原因,我们可以使用+操作符。

assertEquals(
	listOf(1, 2, 3, 4, 5, 6, 7, 8), 
	list + listOf(7, 8)
)

zip

返回由pair组成的List,每个pair由两个集合中相同index的元素组成。这个返回的List的大小由最小的那个集合决定。

assertEquals(
	listOf(Pair(1, 7), Pair(2, 8)), 
	list.zip(listOf(7, 8))
)

unzip

从包含pair的List中生成包含List的Pair。

assertEquals(
	Pair(listOf(5, 6), listOf(7, 8)), 
	listOf(Pair(5, 7), Pair(6, 8)).unzip()
)

作者:chzphoenix 发表于2017/9/22 17:53:48 原文链接
阅读:0 评论:0 查看评论

kotlin集合操作符——顺序操作符

$
0
0

Kotlin学习笔记系列:http://blog.csdn.net/column/details/16696.html

关于集合的操作符,直接引用书上的内容,基本上总结的很好了。

reverse

返回一个与指定list相反顺序的list。

val unsortedList = listOf(3, 2, 7, 5)
assertEquals(listOf(5, 7, 2, 3), unsortedList.reverse())

sort

返回一个自然排序后的list。

assertEquals(listOf(2, 3, 5, 7), unsortedList.sort())

sortBy

返回一个根据指定函数排序后的list。

assertEquals(listOf(3, 7, 2, 5), unsortedList.sortBy { it % 3 })

sortDescending

返回一个降序排序后的List。

assertEquals(listOf(7, 5, 3, 2), unsortedList.sortDescending())

sortDescendingBy

返回一个根据指定函数降序排序后的list。

assertEquals(listOf(2, 5, 7, 3), unsortedList.sortDescendingBy { it % 3 })

作者:chzphoenix 发表于2017/9/22 17:55:08 原文链接
阅读:0 评论:0 查看评论

android视频播放器缓存

$
0
0

今天介绍一个视频离线缓存的框架,由于视频播放的时候下载多次是没有意义的。今天介绍的AndroidVideoCache支持VideoView/MediaPlayer, ExoPlayer等播放器实现离线缓存功能。

主要特征:

  • 流媒体磁盘缓存;
  • 资源离线缓存;
  • 局部加载;
  • 缓存限制 (最大缓存大小, 最大文件数);
  • 支持多客户端.

注意,AndroidVideoCache只对媒体文件使用直接url,它不支持任何流技术,如DASH,平滑流,HLS。


开始使用

增加依赖

dependencies {
    compile 'com.danikula:videocache:2.7.0'
}

并使用代理的url代替原来的url来添加缓存:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

    HttpProxyCacheServer proxy = getProxy();
    String proxyUrl = proxy.getProxyUrl(VIDEO_URL);
    videoView.setVideoPath(proxyUrl);
}

private HttpProxyCacheServer getProxy() {
    // should return single instance of HttpProxyCacheServer shared for whole app.
}

为了保证正常工作,你应该在整个应用程序中使用一个HttpProxyCacheServer的实例。

public class App extends Application {

    private HttpProxyCacheServer proxy;

    public static HttpProxyCacheServer getProxy(Context context) {
        App app = (App) context.getApplicationContext();
        return app.proxy == null ? (app.proxy = app.newProxy()) : app.proxy;
    }

    private HttpProxyCacheServer newProxy() {
        return new HttpProxyCacheServer(this);
    }
}

或者使用简单工厂。更可取的方法是使用像Dagger这样的依赖注入器。


方法

磁盘缓存

默认情况下,HttpProxyCacheServer使用512Mb的缓存文件。你可以改变这个值:

private HttpProxyCacheServer newProxy() {
    return new HttpProxyCacheServer.Builder(this)
            .maxCacheSize(1024 * 1024 * 1024)       // 1 Gb for cache
            .build();
}

或者可以限制缓存中的文件总数:

private HttpProxyCacheServer newProxy() {
    return new HttpProxyCacheServer.Builder(this)
            .maxCacheFilesCount(20)
            .build();
}

甚至实施你自己的DiskUsage策略:

private HttpProxyCacheServer newProxy() {
    return new HttpProxyCacheServer.Builder(this)
            .diskUsage(new MyCoolDiskUsageStrategy())
            .build();
}

监听缓存进度

使用HttpProxyCacheServer。registerCacheListener(CacheListener listener)方法,可以用回调onCacheAvailable(File cacheFile,String url,int percentsAvailable)来设置监听器,以了解缓存的进度。不要忘记在HttpProxyCacheServer的帮助下取消订阅侦听器。unregisterCacheListener(CacheListener listener)方法以避免内存泄漏。

使用HttpProxyCacheServer。isCached(String url)方法检查url的内容是否完全缓存到文件中。

为缓存的文件提供名称

默认情况下,AndroidVideoCache使用MD5的视频url作为文件名。但在某些情况下,url不稳定,它可以包含一些生成的部分(例如会话标记)。在这种情况下,缓存机制将被破坏。要修复它,您必须提供自己的文件生成器:

public class MyFileNameGenerator implements FileNameGenerator {

    // Urls contain mutable parts (parameter 'sessionToken') and stable video's id (parameter 'videoId').
    // e. g. http://example.com?videoId=abcqaz&sessionToken=xyz987
    public String generate(String url) {
        Uri uri = Uri.parse(url);
        String videoId = uri.getQueryParameter("videoId");
        return videoId + ".mp4";
    }
}

...
HttpProxyCacheServer proxy = HttpProxyCacheServer.Builder(context)
    .fileNameGenerator(new MyFileNameGenerator())
    .build()

添加自定义http标头

您可以在HeadersInjector的帮助下添加定制的标题:

public class UserAgentHeadersInjector implements HeaderInjector {

    @Override
    public Map<String, String> addHeaders(String url) {
        return Maps.newHashMap("User-Agent", "Cool app v1.1");
    }
}

private HttpProxyCacheServer newProxy() {
    return new HttpProxyCacheServer.Builder(this)
            .headerInjector(new UserAgentHeadersInjector())
            .build();
}

使用 exoPlayer

您可以使用exoPlayer和AndroidVideoCache。参见exoPlayer分支中的示例应用程序。注释exoPlayer也支持缓存。

例子

详细使用方法见sample

项目地址

AndroidVideoCache : https://github.com/danikula/AndroidVideoCache

作者:gsg8709 发表于2017/9/22 18:03:58 原文链接
阅读:235 评论:0 查看评论

2. Add Two Numbers。

$
0
0

You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.

You may assume the two numbers do not contain any leading zero, except the number 0 itself.

Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8


计算两个链表的和,各个节点进行相加,然后有需要进位的则进位到下一个节点上。这个题的思路没有多难,无非就是遍历两个链表,对他们的数值进行相加运算,用一个变量来保存相加之后的总和。使用另一个变量来记录进位的大小。然后每次创建一个新的节点,节点的数值就是总和对于10的余数。需要注意的就是最后面所有相加完成了可能会多出一个节点,因为最后的两个数值可能相加大于10这时候用来保存进行的就需要多加一个节点。还有一点需要注意就是两个节点的长度可能不相同,所以如果节点没有内容了,可以使用0来代替。

#include <iostream>
#include <unordered_set>
using namespace std;

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* result = new ListNode(0);//作为新链表的头结点
        ListNode* p = result;
        int carry = 0;//用来保留两个数相加之后进位的大小
        int sum,n1,n2;

        while(l1 || l2) {
            if(l1) {
                n1 = l1->val;
            }else {
                n1 = 0;
            }

            if(l2) {
                n2 = l2->val;
            }else {
                n2 = 0;
            }
            sum = n1 + n2 + carry;
            carry = sum / 10;

            p->next = new ListNode(sum % 10);
            p = p->next;

            if(l1)
                l1 = l1->next;
            if(l2)
                l2 = l2->next;

        }
        if(carry) {
            p->next = new ListNode(carry);
        }

        return result->next;

    }
};

int main() {
    Solution s;


    ListNode node1(1);
    ListNode node2(3);

    ListNode node3(9);
    ListNode node4(9);

    //node1.next = &node2;

    node3.next = &node4;

    ListNode* p = s.addTwoNumbers(&node1,&node3);

    while(p) {
        cout << p->val;
        cout << endl;
        p = p->next;
    }


}


运行结果可能有误差。

这里写图片描述

作者:Leafage_M 发表于2017/9/22 19:37:57 原文链接
阅读:229 评论:0 查看评论

二维码的扩展 - 二维码实现多端跳转和多应用配置

$
0
0

二维码的扩展 - 二维码实现多端跳转和多应用配置

1.应用场景

一个APP
安卓端一个二维码,苹果端一个二维码
如果安卓端要做多应用市场跳转,则需要更多的二维码

最好有一个二维码,安卓设备(手机,pad等)扫码安装安卓应用,苹果设备(iphone,ipad等)扫码指向苹果应用商店的指定应用。
另外,这个二维码最好具有极高的识别率,一次扫码的成功率极高,避免多次扫码的发生。

2.多端跳转的组成图

这里写图片描述

3.测试用例

点击识别设备

扫码可以进行设备识别
扫码识别设备

4.设备识别代码

// JavaScript代码
if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
  alert('你的设备的IOS'); 
} else if (/(Android)/i.test(navigator.userAgent)) {  
  alert('你的设备是android'); 
} else { 
  alert('你的设备的pc'); 
};

5.二维码扫描后的运行路径

这里写图片描述

6.制作二维码

1.到APP store点击分享APP。得到APP在APP store的精准链接。这个链接可以直达这个应用。
2.安卓端的APK文件放置在服务器的一个文件夹,可以直接访问并下载,并获取链接。
3.在服务端写一个空白页面,用于设备识别,页面插入如下的JS代码,并获取链接。

// JavaScript代码
if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) { 
  window.location.href = '第一步获取到的链接';
} else if (/(Android)/i.test(navigator.userAgent)) {  
  window.location.href = '第二步获取到的链接';
} else { 
  window.location.href = 'APP的官方网站,或者其它';
};

4.把第三步获取的链接进行网址缩短,得到一个短链。
5.把这个短链生成一个二维码,并加上LOGO。

7.多应用模式

一个应用的多端可以用一个二维码来解决,多个应用也可以用一个二维码来解决并支持多端识别。
即:一个二维码,扫描支持多个APP,并且支持多端。

在制作二维码的时候,第三步提到的空白页面可以利用起来,而不仅仅只是做一个跳转支持。
这里写图片描述
在这里做一个页面,提供点击。
把上面的自动跳转封装成一个函数。

// 应用A
function jumpA(){navigation('','','');}
// 应用B
function jumpB(){navigation('','','');}
// 应用C
function jumpC(){navigation('','','');}
// 应用D
function jumpD(){navigation('','','');}

function navigation(androidURL,IOSURL,pcURL){
    if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) { 
      window.location.href = IOSURL;
    } else if (/(Android)/i.test(navigator.userAgent)) {  
      window.location.href = androidURL;
    } else { 
      window.location.href = pcURL;
    };
}
作者:qq_15071263 发表于2017/9/22 19:45:28 原文链接
阅读:396 评论:1 查看评论

445. Add Two Numbers II。

$
0
0

You are given two non-empty linked lists representing two non-negative integers. The most significant digit comes first and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.

You may assume the two numbers do not contain any leading zero, except the number 0 itself.

Follow up:
What if you cannot modify the input lists? In other words, reversing the lists is not allowed.

Example:

Input: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 8 -> 0 -> 7


这个题跟Add Two Numbers是一样的,但是这个是需要从链表的最后面开始进行相加。我的思路就是使用栈的特性,遍历两个链表,然后分别将两个链表中的内容存放到两个栈中,然后再根据栈中的数据分别出栈,这样每次出栈的元素的顺序就是原先链表从后往前的顺序。再使用跟之前那个一样的算数体系,使用一个变量保存两个数的总和,一个变量保存进位的数值。不断的创建新的节点,这里需要使用头插法来连接链表。

#include <iostream>
#include <stack>
using namespace std;

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {

        ListNode* result = new ListNode(0);
        stack<int> nodes1;
        stack<int> nodes2;
        int carry = 0;
        int sum;

        while(l1 || l2) {

            if(l1) {
                nodes1.push(l1->val);
                l1 = l1->next;
            }

            if(l2) {
                nodes2.push(l2->val);
                l2 = l2->next;
            }
        }

        int n1,n2;
        while(!nodes1.empty() || !nodes2.empty()) {//因为两个栈的元素一样多


            if(nodes1.empty()) {
                n1 = 0;
            } else {
                n1 = nodes1.top();
                nodes1.pop();
            }

            if(nodes2.empty()) {
                n2 = 0;
            } else {
                n2 = nodes2.top();
                nodes2.pop();
            }

            //cout << n1 << "," << n2 << endl;


            sum = n1 + n2 + carry;

            //cout << "sum:" << sum << endl;

            carry = sum / 10;//进的位数

            result->val = sum % 10;//最后面一个节点赋值
            ListNode* node = new ListNode(0);
            node->next = result;
            result = node;
        }

        if(carry) {
            result->val = carry;
            return result;
        } else {
            return result->next;
        }
    }
};

int main() {
    Solution s;


    ListNode node1(7);
    ListNode node2(2);
    ListNode node3(4);
    ListNode node4(3);

    ListNode node5(5);
    ListNode node6(6);
    ListNode node7(4);

    node1.next = &node2;
    node2.next = &node3;
    node3.next = &node4;

    node5.next = &node6;
    node6.next = &node7;

    ListNode* p = s.addTwoNumbers(&node1,&node5);
    while(p) {
        cout << p->val << endl;
        p = p->next;
    }

}


运行结果可能有误差。

这里写图片描述

作者:Leafage_M 发表于2017/9/22 19:48:09 原文链接
阅读:246 评论:0 查看评论

328. Odd Even Linked List。

$
0
0

Given a singly linked list, group all odd nodes together followed by the even nodes. Please note here we are talking about the node number and not the value in the nodes.

You should try to do it in place. The program should run in O(1) space complexity and O(nodes) time complexity.

Example:
Given 1->2->3->4->5->NULL,
return 1->3->5->2->4->NULL.


这题比较简单。就是将奇数节点和偶数节点进行分离,然后奇数串在一起,偶数串在一起,最后偶数跟在奇数的后面就好了。使用一个int变量从1开始进行+1,就可以使用对2取模运算根据计算结果是1还是0就好了。然后分别将奇数偶数串起来就好了,需要注意一下最后一个偶数节点(因为偶数在奇数后面)可能next指向的不为空,这时候需要手动赋值为空。

#include <iostream>
#include <unordered_set>
using namespace std;

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if(!head) {
            return NULL;
        }

        ListNode* oddNode = NULL;//奇数节点
        ListNode* evenNode = NULL;//偶数节点
        ListNode* p1;
        ListNode* p2;
        int num = 1;

        while(head) {
            if(num%2 == 1) {//奇数节点
                if(!oddNode) {
                    oddNode = head;
                } else {
                    p1->next = head;
                }
                p1 = head;
            }
            if(num%2 == 0) {//偶数节点
                if(!evenNode) {
                    evenNode = head;
                } else {
                    p2->next = head;
                }
                p2 = head;
            }
            //cout << p1->val << ",";
            //cout << p2->val << endl;
            head = head->next;
            num++;
        }
        //cout << p1->val << ",";
        //cout << p2->val << endl;
        //cout << oddNode->val << ",";
        //cout << evenNode->val << endl;
        p1->next = evenNode;
        p2->next = NULL;
        return oddNode;
    }
};

int main() {
    Solution s;


    ListNode node1(1);
    ListNode node2(2);
    ListNode node3(3);
    ListNode node4(4);
    ListNode node5(5);

    node1.next = &node2;
    node2.next = &node3;
    node3.next = &node4;
    node4.next = &node5;

    ListNode* p = s.oddEvenList(&node1);

    while(p) {
        cout << p->val;
        cout << endl;
        p = p->next;
    }

}


运行结果可能有误差。

这里写图片描述

作者:Leafage_M 发表于2017/9/22 19:57:17 原文链接
阅读:266 评论:0 查看评论

24. Swap Nodes in Pairs。

$
0
0

Given a linked list, swap every two adjacent nodes and return its head.

For example,
Given 1->2->3->4, you should return the list as 2->1->4->3.

Your algorithm should use only constant space. You may not modify the values in the list, only nodes itself can be changed.


这个题就是将临近的节点进行交换。理论上来说并不困难,想到了两种方法,但是效率都不高。。。
第一个就是直接在原先链表上进行交换,先标记当前节点的下一个节点,然后让当前节点直接指向下一个节点的下一个,就可以直接下一个节点隔开了。然后让当前节点的下一个节点指向当前节点。就完成了交换,需要再额外使用一个标记来记录交换位置之后的链表最后一个节点在哪。同时注意一下不要出现空指针异常就好了。

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(!head || !head->next) {
            return head;
        }
        ListNode* p1 = NULL;
        ListNode* p2 = NULL;
        ListNode* tail = NULL;
        ListNode* result = head->next;

        while(head) {
            p1 = head;
            p2 = head->next;
            if(p2) {
                p1->next = p2->next;
                p2->next = p1;
            } else {
                tail->next = p1;
                break;
            }
            if(tail) {
                tail->next = p2;
                tail = p1;
            } else {
                tail = p1;
            }
            head = head->next;
        }
        return result;
    }
};

另一种方法就是使用queue的特性,先将后一个节点加入队列,然后将当前节点再加入队列,相当于将链表遍历一遍,然后遍历的同时交换位置存入队列。再遍历队列将各个节点串起来。

#include <iostream>
#include <queue>
using namespace std;

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(!head || !head->next) {
            return head;
        }

        queue<ListNode*> nodes;
        ListNode* start,* tail;

        while(head) {
            if(head->next) {
                nodes.push(head->next);
                //cout << "next:" << head->next->val << ",";
            }
            nodes.push(head);
            //cout << "head:" << head->val << endl;

            if(head->next) {
                head = head->next->next;
            } else {
                break;
            }
        }

        start = nodes.front();
        tail = start;
        nodes.pop();
        while(!nodes.empty()) {
            //cout << "queue:" << nodes.front()->val << ",";
            tail->next = nodes.front();
            tail = nodes.front();
            nodes.pop();
        }
        tail->next = NULL;
        return start;

        /*ListNode* tail = NULL;//用来指向交换后最后一个
        ListNode* temp;//用来标记两个需要交换的节点的第二个,head是第一个
        ListNode* result = head->next;


        while(head->next) {
            temp = head->next;
            head->next = temp->next;
            temp->next = head;

            if(tail) {
                tail->next = temp;
                tail = head;
            } else {
                tail = head;
            }
            head = head->next;
            if(!head) {
                break;
            }

        }
        return result;*/
    }
};

int main() {
    Solution s;


    ListNode node1(1);
    ListNode node2(2);
    ListNode node3(3);
    ListNode node4(4);
    ListNode node5(5);

    node1.next = &node2;
    //node2.next = &node3;
    //node3.next = &node4;
    //node4.next = &node5;

    ListNode* p = s.swapPairs(&node1);

    while(p) {
        cout << p->val;
        cout << endl;
        p = p->next;
    }

}


运行结果可能有误差。

这里写图片描述

作者:Leafage_M 发表于2017/9/22 20:04:36 原文链接
阅读:273 评论:0 查看评论

1. Two Sum。

$
0
0

Given an array of integers, return indices of the two numbers such that they add up to a specific target.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

Example:

Given nums = [2, 7, 11, 15], target = 9,

Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].


这道题是让在一个数组中找到两个相加能够与给定目标数相等的下标。可以是用map结构,然后遍历数组中的元素,拿到数值之后将其作为key在map中查找。如果在map中没有找到这个数值,则将目标数减去这个数值得到的结果作为key,这个数值的下标作为value存放在map中,然后继续往后面遍历。如果遇到了另一个数正好等于目标数减去刚才那个数,那么这个数作为key在map中就会存在。比如:目标数为3,数组第一个数为1,那么存放在map中的元素就是<2,0>,然后第二个数为2,此时发现map中已经有了<2,0>那么当前的下标和map中的下标就是我们所要求的结果。

#include <iostream>
#include <vector>
#include <unordered_map>

using namespace std;

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> ret;
        unordered_map<int,int> m;
        if (!nums.empty()) {
            int i;
            for (i = 0; i < nums.size(); i++) {
                if (m.find(nums[i]) == m.end()) {//此时map中不存在此hash
                    m[target-nums[i]] = i;
                } else {
                    ret.push_back(m[nums[i]]);//另一个的下标索引
                    ret.push_back(i);//此时的下标索引
                    return ret;
                }
            }
        }
        return ret;
    }
};

int main() {
    Solution s;
    vector<int> num;
    num.push_back(2);
    num.push_back(7);
    num.push_back(11);
    num.push_back(15);
    vector<int> ret = s.twoSum(num,9);
    cout << ret[0] << " " << ret[1];
}

运行结果可能有误差。

这里写图片描述

作者:Leafage_M 发表于2017/9/22 21:06:35 原文链接
阅读:263 评论:0 查看评论

633. Sum of Square Numbers。

$
0
0

Given a non-negative integer c, your task is to decide whether there’re two integers a and b such that a2 + b2 = c.

Example 1:

Input: 5
Output: True
Explanation: 1 * 1 + 2 * 2 = 5

Example 2:

Input: 3
Output: False


让找出有没有平方和等于目标数。可以采用两边向中间扫描的查找方式,在0和目标数的开方之间。

#include <iostream>
#include <math.h>

using namespace std;

class Solution {
public:
    bool judgeSquareSum(int c) {
        int a = sqrt(c);
        int b = 0;

        while (a >= b) {
            if (a*a + b*b == c) {
                return true;
            } else {
                if (a*a + b*b < c) {
                    b++;
                } else {
                    a--;
                }
            }

        }
        return false;

    }
};

int main() {
    Solution s;
    s.judgeSquareSum(3);
}

运行结果可能有误差。

这里写图片描述

作者:Leafage_M 发表于2017/9/22 21:15:17 原文链接
阅读:265 评论:0 查看评论

Android自定义View_底部弹出Popuwindow

$
0
0

从底部弹出PopuWindow在开发中是一个经常用到的问题,代码枯燥,又没有什么技术含量,我就把它封装了一下,以最简单的方式实现它.

看下效果图

这里写图片描述

实现方式

基础类

package cn.yuan.xiaoyu.testmodule.view.picker;

import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.support.annotation.LayoutRes;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.PopupWindow;

import cn.yuan.xiaoyu.R;

/**
 * Created by yukuoyuan on 2017/9/23.
 * 这是一个底部的最基础的弹窗
 */
public abstract class BasePopupWindow extends PopupWindow implements View.OnClickListener {
    /**
     * 上下文
     */
    private Context context;
    /**
     * 最上边的背景视图
     */
    private View vBgBasePicker;
    /**
     * 内容viewgroup
     */
    private LinearLayout llBaseContentPicker;

    public BasePopupWindow(Context context) {
        super(context);
        this.context = context;
        View view = View.inflate(context, R.layout.picker_base, null);
        vBgBasePicker = view.findViewById(R.id.v_bg_base_picker);
        llBaseContentPicker = (LinearLayout) view.findViewById(R.id.ll_base_content_picker);
        /***
         * 添加布局到界面中
         */
        llBaseContentPicker.addView(View.inflate(context, getContentViews(), null));
        setContentView(view);
        //设置PopupWindow弹出窗体的宽
        this.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
        //设置PopupWindow弹出窗体的高
        this.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
        setFocusable(true);//设置获取焦点
        setTouchable(true);//设置可以触摸
        setOutsideTouchable(true);//设置外边可以点击
        ColorDrawable dw = new ColorDrawable(0xffffff);
        setBackgroundDrawable(dw);
        //设置SelectPicPopupWindow弹出窗体动画效果
        this.setAnimationStyle(R.style.BottomDialogWindowAnim);
        initView(view);
        initListener();
        initData();
        vBgBasePicker.setOnClickListener(this);
    }

    /**
     * 初始化数据
     */
    protected abstract void initData();

    /**
     * 初始化监听
     */
    protected abstract void initListener();

    /**
     * 初始化view
     *
     * @param view
     */
    protected abstract void initView(View view);

    /**
     * 初始化布局
     *
     * @return
     */
    protected abstract int getContentViews();

    /**
     * 为了适配7.0系统以上显示问题(显示在控件的底部)
     *
     * @param anchor
     */
    @Override
    public void showAsDropDown(View anchor) {
        if (Build.VERSION.SDK_INT >= 24) {
            Rect rect = new Rect();
            anchor.getGlobalVisibleRect(rect);
            int h = anchor.getResources().getDisplayMetrics().heightPixels - rect.bottom;
            setHeight(h);
        }
        super.showAsDropDown(anchor);
    }

    /**
     * 展示在屏幕的底部
     *
     * @param layoutid rootview
     */
    public void showAtLocation(@LayoutRes int layoutid) {
        showAtLocation(LayoutInflater.from(context).inflate(layoutid, null),
                Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 0);
    }

    /**
     * 最上边视图的点击事件的监听
     *
     * @param v
     */
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.v_bg_base_picker:
                dismiss();
                break;
        }
    }
}

用到的布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <View
        android:id="@+id/v_bg_base_picker"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <LinearLayout
        android:id="@+id/ll_base_content_picker"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="vertical" />
</LinearLayout>

用到的动画样式

  <!-- 底部的dialog弹出的动画样式-->
    <style name="BottomDialogWindowAnim" parent="android:Animation">
        <item name="android:windowEnterAnimation">@anim/dialog_enter_anim</item>
        <item name="android:windowExitAnimation">@anim/dialog_exit_anim</item>
    </style>

展示的时候的动画

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <translate
        android:duration="400"
        android:fromYDelta="1080"
        android:toYDelta="0" />
</set>

退出时的动画

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >

    <translate
        android:fromYDelta="0"   
        android:toYDelta="1080"   
        android:duration="400" />

</set>

通过以上的步骤,我们最基础的从底部弹出的popuwindow就算封装好了

如何使用?

我们以ios的选择弹窗为例

package cn.yuan.xiaoyu.testmodule.view.picker;

import android.content.Context;
import android.view.View;

import cn.yuan.xiaoyu.R;

/**
 * Created by yukuoyuan on 2017/9/23.
 */

public class IOsCheckView extends BasePopupWindow {
    public IOsCheckView(Context context) {
        super(context);
    }

    @Override
    protected void initData() {

    }

    @Override
    protected void initListener() {

    }

    @Override
    protected void initView(View view) {

    }

    @Override
    protected int getContentViews() {
        return R.layout.picker_time;
    }
}

布局代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">


    <TextView
        android:layout_width="match_parent"
        android:layout_height="42dp"
        android:background="@android:color/black"
        android:gravity="center"
        android:text="测试1"
        android:textColor="@android:color/white" />

    <View
        android:layout_width="match_parent"
        android:layout_height="1px" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="42dp"
        android:background="@android:color/black"
        android:gravity="center"
        android:text="测试2"
        android:textColor="@android:color/white" />
</LinearLayout>

展示调用

 IOsCheckView timePickerView = new IOsCheckView(this);
                if (timePickerView != null && !timePickerView.isShowing()) {
                    /**
                     * 参数为你当前Activity或者Fragment的布局
                     */
                    timePickerView.showAtLocation(R.layout.activity_test);
                }

ok就可以看到最开始我们的那个效果图了.有什么问题,欢迎留言给我,谢谢


联系方式

本人技术有限,还有很多不完美的地方,欢迎指出.(写作不易,谢谢您的star支持)

作者:EaskShark 发表于2017/9/23 11:02:13 原文链接
阅读:70 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>