iOS 高仿:花田小憩3.0.1

前言

 

断断续续的已经学习Swift一年多了, 从1.2到现在的2.2, 一直在语法之间徘徊, 学一段时间, 工作一忙, 再捡起来隔段时间又忘了.思来想去, 趁着这两个月加班不是特别多, 就决定用swift仿写一个完整项目.

 

花田小憩:是一个植物美学生活平台,

以自然生活为主导,

提倡植物学生活方法,

倡导美学标准的生活态度的一个APP.

 

个人文字功底有限, 就我而言, 这款APP做的挺唯美的…

 

github地址

https://github.com/SunLiner/Floral

 

声明

 

此花田小憩项目里面的都是真实接口, 真实数据, 仅供学习, 毋作其他用途!!!

 

项目部分截图

 

由于项目的大体功能都已经实现了的, 所以整个项目还是比较庞大的.所以, 下面罗列部分功能的截图.

由于gif录制的时候, 会重新渲染一遍图片, 所以导致项目中用到高斯模糊的地方, 看起来感觉比较乱, 实际效果还是不错的.

 

新特性

 

详情页

 

 

更多项目截图请点击原文查看

 

项目环境

 

编译器 : Xcode7.3及以上

 

语言 : Swift2.2

 

整个项目都是采用纯代码开发模式

 

tip: 之前编译环境这儿有点错误, 因为我项目中用了Swift2.2的特性, 2.2之后方法名需要写成#selector(AddAddressViewController.save), 不再使用双引号了

 

第三方框架

 

use_frameworks!

platform :ios, "8.0"

 

target 'Floral' do

 

pod 'SnapKit', '~> 0.20.0' ## 自动布局

pod 'Alamofire', '~> 3.3.1' ## 网络请求, swift版的AFN

pod 'Kingfisher', '~> 2.3.1' ## 轻量级的SDWebImage

 

end

 

还用到了MBProgressHUD.

除此之外,几乎全部都是自己造的小轮子…

 

目录结构详解

 

Classes下包含7个功能目录:

 

①Resources : 项目用到的资源,包含plist文件, js文件和字体

 

②Network : 网络请求, 所有的网络请求都在这里面, 接口和参数都有详细的注释

 

③Tool : 包含tools(工具类), 3rdLib(第三方:友盟分享, MBProgressHUD ), Category(所有项目用到的分类)

 

④Home : 首页(专题), 包含专题分类, 详情, 每周Top10, 评论, 分享等等功能模块

 

⑤Main : UITabBarController, UINavigationController设置以及新特性

 

⑥Malls : 商城, 包含商城分类, 商品搜索, 详情, 购物车, 购买, 订单, 地址管理, 支付等等功能模块

 

⑦Profile : 个人中心, 专栏作者, 登录/注册/忘记密码, 设置等功能模块

 

大家可以下载项目, 对照这个目录结构进行查看, 很典型的MVC文件结构, 还是很方便的.

 

项目部分功能模块详解

 

① 新特性NewFeatureViewController : 这个功能模块还是比较简单的, 用到了UICollectionViewController, 然后自己添加了UIPageControl, 只需要监听最后一个cell的点击即可.

 

这儿有一个注意点是: 我们需要根据版本号来判断是进入新特性界面, 广告页还是首页.

 

private let SLBundleShortVersionString = "SLBundleShortVersionString"

    // MARK: - 判断版本号

  private func toNewFeature() -> Bool

    {

        // 根据版本号来确定是否进入新特性界面

        let currentVersion = NSBundle.mainBundle().infoDictionary!["CFBundleShortVersionString"] as! String

        let oldVersion = NSUserDefaults.standardUserDefaults().objectForKey(SLBundleShortVersionString) ?? ""

 

        // 如果当前的版本号和本地保存的版本比较是降序, 则需要显示新特性

        if (currentVersion.compare(oldVersion as! String)) == .OrderedDescending{

            // 保存当前的版本

             NSUserDefaults.standardUserDefaults().setObject(currentVersion, forKey: SLBundleShortVersionString)

            return true

        }

        return false

    }

 

② 下拉刷新RefreshControl : 在这个项目中, 没有用第三方的下拉刷新控件, 而是自己实现了一个简单的下拉刷新轮子, 然后赋值给UITableViewController的public var refreshControl: UIRefreshControl?属性. 主要原理就是判断下拉时的frame变化:

 

// 监听frame的变化

        addObserver(self, forKeyPath: "frame", options:.New, context: nil)

 

// 刷新的时候, 不再进行其他操作

    private var isLoading = false

    override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {

        let y = frame.origin.y

        // 1. 最开始一进来的时候, 刷新按钮是隐藏的, y就是-64, 需要先判断掉, y>=0 , 说明刷新控件已经完全缩回去了...

        if y >= 0 || y == -64

        {

            return

        }

 

        // 2. 判断是否一进来就进行刷新

        if beginAnimFlag && (y == -60.0 || y == -124.0){

            if !isLoading {

                isLoading = true

                animtoringFlag = true

                tipView.beginLoadingAnimator()

            }

            return

        }

 

        // 3. 释放已经触发了刷新事件, 如果触发了, 需要进行旋转

        if refreshing && !animtoringFlag

        {

            animtoringFlag = true

            tipView.beginLoadingAnimator()

            return

        }

 

        if y <= -50 && !rotationFlag

        {

            rotationFlag = true

            tipView.rotationRefresh(rotationFlag)

        }else if(y > -50 && rotationFlag){

            rotationFlag = false

            tipView.rotationRefresh(rotationFlag)

        }

    }

 

③ 高斯模糊: 使用的是系统自带的高斯模糊控件UIVisualEffectView, 它是@available(iOS 8.0, *), 附一段简单的使用代码

 

private lazy var blurView : BlurView = {

        let blur = BlurView(effect: UIBlurEffect(style: .Light))

        blur.categories = self.categories

        blur.delegate = self

        return blur

    }()

 

可以根据alpha = 0.5, 调整alpha来调整模糊效果, gif图中的高斯模糊效果不是很明显, 实际效果特别好.

 

 

④ 商城购物车动画:这组动画还是比较简单的, 直接附代码, 如果有什么疑惑, 可以留言或者私信我

 

// MARK : - 动画相关懒加载

    /// layer

    private lazy var animLayer : CALayer = {

        let layer = CALayer()

        layer.contentsGravity = kCAGravityResizeAspectFill;

        layer.bounds = CGRectMake(0, 0, 50, 50);

        layer.cornerRadius = CGRectGetHeight(layer.bounds) / 2

        layer.masksToBounds = true;

        return layer

    }()

 

    /// 贝塞尔路径

    private lazy var animPath = UIBezierPath()

 

    /// 动画组

    private lazy var groupAnim : CAAnimationGroup = {

        let animation = CAKeyframeAnimation(keyPath: "position")

        animation.path = self.animPath.CGPath

        animation.rotationMode = kCAAnimationRotateAuto

 

        let expandAnimation = CABasicAnimation(keyPath: "transform.scale")

        expandAnimation.duration = 1

        expandAnimation.fromValue = 0.5

        expandAnimation.toValue = 2

        expandAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)

 

        let narrowAnimation = CABasicAnimation(keyPath: "transform.scale")

        // 先执行上面的, 然后再开始

        narrowAnimation.beginTime = 1

        narrowAnimation.duration = 0.5

        narrowAnimation.fromValue = 2

        narrowAnimation.toValue = 0.5

        narrowAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)

 

        let groups = CAAnimationGroup()

        groups.animations = [animation,expandAnimation,narrowAnimation]

        groups.duration = 1.5

        groups.removedOnCompletion = false

        groups.fillMode = kCAFillModeForwards

        groups.delegate = self

        return groups

    }()

 

 

    // MARK: - 点击事件处理

    private var num = 0

    func gotoShopCar() {

        if num >= 99 {

            self.showErrorMessage("亲, 企业采购请联系我们客服")

            return

        }

        addtoCar.userInteractionEnabled = false

 

        // 设置layer

        // 贝塞尔弧线的起点

        animLayer.position = addtoCar.center

        layer.addSublayer(animLayer)

        // 设置path

        animPath.moveToPoint(animLayer.position)

 

        let controlPointX = CGRectGetMaxX(addtoCar.frame) * 0.5

 

        // 弧线, controlPoint基准点, endPoint结束点

        animPath.addQuadCurveToPoint(shopCarBtn.center, controlPoint: CGPointMake(controlPointX, -frame.size.height * 5))

 

        // 添加并开始动画

        animLayer.addAnimation(groupAnim, forKey: "groups")

    }

 

    // MARK: - 动画的代理

    // 动画停止的代理

    override func animationDidStop(anim: CAAnimation, finished flag: Bool) {

        if anim ==  animLayer.animationForKey("groups")!{

            animLayer.removeFromSuperlayer()

            animLayer.removeAllAnimations()

 

            num += 1

            shopCarBtn.num = num

 

            let animation = CATransition()

            animation.duration = 0.25

 

            shopCarBtn.layer.addAnimation(animation, forKey: nil)

 

            let shakeAnimation = CABasicAnimation(keyPath: "transform.translation.y")

            shakeAnimation.duration = 0.25

            shakeAnimation.fromValue = -5

            shakeAnimation.toValue = 5

            shakeAnimation.autoreverses = true

 

            shopCarBtn.layer .addAnimation(shakeAnimation, forKey: nil)

            addtoCar.userInteractionEnabled = true

 

        }

    }

 

⑤ 主题详情页:商城详情页的做法也是差不多的, 不过更简单一点.

 

 

关键一点在于, 详情页的展示主要依靠于H5页面. 而我们需要根据webview的高度来确定webviewCell的高度.我的做法是监听UIWebView的webViewDidFinishLoad, 取出webView.scrollView.contentSize.height然后给详情页发送一个通知, 让其刷新界面. 暂时没有想到更好的方法, 如果您有更好的做法, 请务必告诉我, 谢谢…

 

⑥ UIWebView中图片的点击

 

第①步: 我们创建一个image.js文件, 代码如下:

 

//setImage的作用是为页面的中img元素添加onClick事件,即设置点击时调用imageClick

function setImageClick(){

    var imgs = document.getElementsByTagName("img");

    for (var i=0;i<imgs.length;i++){

        var src = imgs[i].src;

        imgs[i].setAttribute("onClick","imageClick(src)");

    }

    document.location = imageurls;

}

 

//imageClick即图片 onClick时触发的方法,document.location = url;的作用是使调用

//webView: shouldStartLoadWithRequest: navigationType:方法,在该方法中我们真正处理图片的点击

function imageClick(imagesrc){

    var url="imageClick::"+imagesrc;

    document.location = url;

}

 

第②步:在UIWebView的代理方法webViewDidFinishLoad中, 加载JS文件, 并给图片绑定绑定点击事件

 

// 加载js文件

        webView.stringByEvaluatingJavaScriptFromString(try! String(contentsOfURL: NSBundle.mainBundle().URLForResource("image", withExtension: "js")!, encoding: NSUTF8StringEncoding))

 

        // 给图片绑定点击事件

        webView.stringByEvaluatingJavaScriptFromString("setImageClick()")

 

第③步:在UIWebView的代理方法-webView:shouldStartLoadWithRequest:navigationType:中判断图片的点击

 

func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {

        let urlstr = request.URL?.absoluteString

        let components : [String] = urlstr!.componentsSeparatedByString("::")

        if (components.count >= 1) {

            //判断是不是图片点击

            if (components[0] == "imageclick") {

                parentViewController?.presentViewController(ImageBrowserViewController(urls: [NSURL(string: components.last!)!], index: NSIndexPath(forItem: 0, inSection: 0)), animated: true, completion: nil)

                return false;

            }

            return true;

        }

        return true

    }

 

 

⑦ 登录/注册/忘记密码:

 

眼尖一点的朋友可能在上面的gif中已经发现, 花田小憩中的登录/注册/忘记密码界面几乎是一样的, 我的做法是用一个控制器LoginViewController来代表登录/注册/忘记密码三个功能模块, 通过两个变量isRegister和isRevPwd来判断是哪个功能, 显示哪些界面, 我们点击注册和忘记密码的时候, 会执行代理方法:

 

// MARK: - LoginHeaderViewDelegate

    func loginHeaderView(loginHeaderView : LoginHeaderView, clickRevpwd pwdBtn: UIButton) {

        let login = LoginViewController()

        login.isRevPwd = true

        navigationController?.pushViewController(login, animated: true)

    }

 

    func loginHeaderView(loginHeaderView : LoginHeaderView, clickRegister registerbtn: UIButton) {

        let login = LoginViewController()

        login.isRegister = true

        navigationController?.pushViewController(login, animated: true)

    }

 

⑧ 验证码的倒计时功能

 

 

/// 点击"发送验证码"按钮

    func clickSafeNum(btn: UIButton) {

        var seconds = 10 //倒计时时间

        let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

        let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);

        dispatch_source_set_timer(timer,dispatch_walltime(nil, 0),1 * NSEC_PER_SEC, 0); //每秒执行

        dispatch_source_set_event_handler(timer) {

            if(seconds<=0){ //倒计时结束,关闭

                dispatch_source_cancel(timer);

                dispatch_async(dispatch_get_main_queue(), {

                    //设置界面的按钮显示 根据自己需求设置

                    btn.setTitleColor(UIColor.blackColor(), forState:.Normal)

                    btn.setTitle("获取验证码", forState:.Normal)

                    btn.titleLabel?.font = defaultFont14

                    btn.userInteractionEnabled = true

                    });

            }else{

 

                dispatch_async(dispatch_get_main_queue(), {

                    UIView.beginAnimations(nil, context: nil)

                    UIView.setAnimationDuration(1)

                })

                dispatch_async(dispatch_get_main_queue(), {

                    //设置界面的按钮显示 根据自己需求设置

                    UIView.beginAnimations(nil, context: nil)

                    UIView.setAnimationDuration(1)

                    btn.setTitleColor(UIColor.orangeColor(), forState:.Normal)

                    btn.setTitle("\(seconds)秒后重新发送", forState:.Normal)

                    btn.titleLabel?.font = UIFont.systemFontOfSize(11)

                    UIView.commitAnimations()

                    btn.userInteractionEnabled = false

 

                })

               seconds -= 1

 

        }

 

    }

    dispatch_resume(timer)

}

 

⑨ 设置模块中给我们评分

 

这个功能在实际开发中特别常见:

 

给我们评分

 

代码如下, 很简单:

 

UIApplication.sharedApplication().openURL(NSURL(string: "itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=998252000")!)

 

其中最后的id需要填写你自己的APP在AppStore中的id, 打开iTunes找到你自己的APP或者你想要的APP, 就能查看到id.

 

tip: 此功能测试的时候, 必须用真机!!!

 

⑩ 登录状态.

 

我们可以通过NSHTTPCookieStorage中的NSHTTPCookie来判断登录状态.也可以自定义一个字段来保存. 根据我抓包得知, 花田小憩APP的做法是第一次登录后保存用户名和密码(MD5加密的, 我测试过), 然后每次启动应用程序的时候, 会首先后台自动登录, 然后在进行评论/点赞等操作的时候呢, 参数中会带上用户的id.由于涉及到花田小憩的账号密码的一些隐私, 所以登录/注册模块, 我就没有没有完整的写出来. 有兴趣的朋友可以私信我, 我可以把接口给你, 在此声明: 仅供学习, 毋做伤天害理之事

 

`tip: 我在AppDelegate.swift中给大家留了一个开关, 可以快速的进行登录状态的切换…

 

⑩①: 个人/专栏中心:

 

这两个功能是同一个控制器, 是UICollectionViewController而不是UITableViewController

 

大家对UITableViewController的header应该很熟悉吧, 向上滑动的时候, 会停留在navigationBar的下面, 虽然UICollectionViewController也可以设置header, 但是在iOS9以前, 他是不能直接设置停留的.在iOS9之后, 可以一行代码设置header的停留

 

sectionHeadersPinToVisibleBounds = true

 

但是在iOS9之前, 我们需要自己实现这个功能:

 

//

//  LevitateHeaderFlowLayout.swift

//  Floral

//

//  Created by ALin on 16/5/20.

//  Copyright © 2016年 ALin. All rights reserved.

//  可以让header悬浮的流水布局

 

import UIKit

 

class LevitateHeaderFlowLayout: UICollectionViewFlowLayout {

    override func prepareLayout() {

        super.prepareLayout()

        // 即使界面内容没有超过界面大小,也要竖直方向滑动

        collectionView?.alwaysBounceVertical = true

        // sectionHeader停留

        if #available(iOS 9.0, *) {

            sectionHeadersPinToVisibleBounds = true

        }

    }

 

    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

        // 1. 获取父类返回的UICollectionViewLayoutAttributes数组

        var answer = super.layoutAttributesForElementsInRect(rect)!

 

        // 2. 如果是iOS9.0以上, 直接返回父类的即可. 不用执行下面的操作了. 因为我们直接设置sectionHeadersPinToVisibleBounds = true即可

        if #available(iOS 9.0, *) {

            return answer

        }

 

        // 3. 如果是iOS9.0以下的系统

 

        // 以下代码来源:http://stackoverflow.com/questions/13511733/how-to-make-supplementary-view-float-in-uicollectionview-as-section-headers-do-i%3C/p%3E

        // 目的是让collectionview的header可以像tableview的header一样, 可以停留

 

        // 创建一个索引集.(NSIndexSet:唯一的,有序的,无符号整数的集合)

        let missingSections = NSMutableIndexSet()

        // 遍历, 获取当前屏幕上的所有section

        for layoutAttributes in answer {

            // 如果是cell类型, 就加入索引集里面

            if (layoutAttributes.representedElementCategory == UICollectionElementCategory.Cell) {

                missingSections.addIndex(layoutAttributes.indexPath.section)

            }

        }

 

        // 遍历, 将屏幕中拥有header的section从索引集中移除

        for layoutAttributes in answer {

            // 如果是header, 移掉所在的数组

            if (layoutAttributes.representedElementKind == UICollectionElementKindSectionHeader) {

                missingSections .removeIndex(layoutAttributes.indexPath.section)

            }

        }

 

        // 遍历当前屏幕没有header的索引集

        missingSections.enumerateIndexesUsingBlock { (idx, _) in

            // 获取section中第一个indexpath

            let indexPath = NSIndexPath(forItem: 0, inSection: idx)

            // 获取其UICollectionViewLayoutAttributes

            let layoutAttributes = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: indexPath)

            // 如果有值, 就添加到UICollectionViewLayoutAttributes数组中去

            if let _ = layoutAttributes{

                answer.append(layoutAttributes!)

            }

        }

 

        // 遍历UICollectionViewLayoutAttributes数组, 更改header的值

        for layoutAttributes in answer {

            // 如果是header, 改变其参数

            if (layoutAttributes.representedElementKind==UICollectionElementKindSectionHeader) {

                // 获取header所在的section

                let section = layoutAttributes.indexPath.section

                // 获取section中cell总数

                let numberOfItemsInSection = collectionView!.numberOfItemsInSection(section)

                // 获取第一个item的IndexPath

                let firstObjectIndexPath = NSIndexPath(forItem: 0, inSection: section)

                // 获取最后一个item的IndexPath

                let lastObjectIndexPath = NSIndexPath(forItem: max(0, (numberOfItemsInSection - 1)), inSection: section)

 

                // 定义两个变量来保存第一个和最后一个item的layoutAttributes属性

                var firstObjectAttrs : UICollectionViewLayoutAttributes

                var lastObjectAttrs : UICollectionViewLayoutAttributes

 

                // 如果当前section中cell有值, 直接取出来即可

                if (numberOfItemsInSection > 0) {

                    firstObjectAttrs =

                        self.layoutAttributesForItemAtIndexPath(firstObjectIndexPath)!

                    lastObjectAttrs = self.layoutAttributesForItemAtIndexPath(lastObjectIndexPath)!

                } else { // 反之, 直接取header和footer的layoutAttributes属性

                    firstObjectAttrs = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionHeader, atIndexPath: firstObjectIndexPath)!

                    lastObjectAttrs = self.layoutAttributesForSupplementaryViewOfKind(UICollectionElementKindSectionFooter, atIndexPath: lastObjectIndexPath)!

                }

                // 获取当前header的高和origin

                let headerHeight = CGRectGetHeight(layoutAttributes.frame)

                var origin = layoutAttributes.frame.origin

 

                origin.y = min(// 2. 要保证在即将消失的临界点跟着消失

                    max( // 1. 需要保证header悬停, 所以取最大值

                        collectionView!.contentOffset.y  + collectionView!.contentInset.top,

                        (CGRectGetMinY(firstObjectAttrs.frame) - headerHeight)

                    ),

                    (CGRectGetMaxY(lastObjectAttrs.frame) - headerHeight)

                )

 

                // 默认的层次关系是0. 这儿设置大于0即可.为什么设置成1024呢?因为我们是程序猿...

                layoutAttributes.zIndex = 1024

                layoutAttributes.frame = CGRect(origin: origin, size: layoutAttributes.frame.size)

 

            }

 

        }

 

        return answer;

    }

 

    override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {

        // 返回true, 表示一旦进行滑动, 就实时调用上面的-layoutAttributesForElementsInRect:方法

        return true

    }

 

}

 

⑩+@end:

 

整个项目, 东西还是蛮多的, 也不是仅仅几百上千字能说清楚的, 几乎每一个页面, 每一个文件, 我都有详细的中文注释. 希望大家一起进步. 这也是我的第一个开源的完整的Swift 项目, 有什么不足或者错误的地方, 希望大家指出来, 万分感激!!!

 

下载地址

https://github.com/SunLiner/Floral

 

如果对您有些许帮助, 请☆star

 

后续

 

可能有些功能模块存在bug, 后续我都会一一进行修复和完善的, 并更新在github上.

 

如果您有任何疑问,或者发现bug以及不足的地方, 可以在下面给我留言, 或者关注我的新浪微博, 给我私信.

 

联系我

https://github.com/SunLiner

 

转载于:https://www.cnblogs.com/fengmin/p/5593795.html

<think>首先,用户的问题是关于Android Gradle插件3.0.1的依赖配置。用户说:"我想查找特定版本的Android Gradle插件依赖配置 请问Android Gradle插件3.0.1依赖配置 classpath &#39;com.android.tools.build:gradle:3.0.1&#39;" 用户已经在问题中提供了依赖配置:classpath &#39;com.android.tools.build:gradle:3.0.1&#39;。但用户可能是在确认这个配置是否正确,或者遇到了问题需要解决。 从系统级指令中,我需要: - 所有行内数学表达式使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 用户提供了几个引用,这些引用是关于Could not find com.android.tools.build:gradle:3.0.1的错误。参考这些引用: - 引用[1]:提到在jcenter找不到插件,建议添加google()仓库。[^1] - 引用[2]:建议在build.gradle的buildscript中添加google()。[^2] - 引用[3]:显示在本地缓存中找到了文件,但可能配置问题。[^3] - 引用[4]:添加google()解决了问题。[^4] - 引用[5]:运行命令的建议。[^5] 用户的核心需求是确认Android Gradle插件3.0.1的依赖配置,并可能解决相关错误。 我的回答应该结构清晰,逐步解决问题。既然用户已经提供了依赖配置,我应该确认它,并解释如何正确设置以避免常见错误。 步骤: 1. 确认依赖配置:classpath &#39;com.android.tools.build:gradle:3.0.1&#39; 是正确的。 2. 解释需要在build.gradle文件的buildscript块中添加google()仓库,因为jcenter可能不包含所有版本。 3. 提供完整的示例代码。 4. 生成相关问题。 使用中文回答。 生成相关问题:基于主题,问题应该围绕Android Gradle插件的配置、常见错误、优化等。 回答结构: - 开头:直接回答用户的问题,确认配置。 - 中间:详细解释如何设置,包括示例代码。 - 结尾:生成相关问题。 引用标识:在引用内容末尾添加[^n],但由于用户提供的引用是参考,我需要自然地引用它们。指令说"回答中引用的段落末尾自然地添加引用标识",所以我可以在解释时引用这些建议。 例如,在提到添加google()时,引用[2]或[4]。 完整回答: 1. 确认Android Gradle插件3.0.1的依赖配置是classpath &#39;com.android.tools.build:gradle:3.0.1&#39;。 2. 强调需要在buildscript的repositories中添加google(),因为从引用中看,这是解决找不到插件的关键。 3. 提供build.gradle的完整示例。 4. 生成相关问题。</think>### Android Gradle 插件 3.0.1 依赖配置说明 您询问的 Android Gradle 插件 3.0.1 的依赖配置是正确的:在项目根目录下的 `build.gradle` 文件中,使用 `classpath &#39;com.android.tools.build:gradle:3.0.1&#39;` 即可指定该版本。然而,在实际配置中,许多开发者会遇到类似 "Could not find com.android.tools.build:gradle:3.0.1" 的错误,这通常是由于仓库源缺失或配置不当引起的。下面我将逐步解释如何正确设置,并避免常见问题。 #### 步骤 1: 确认依赖配置的基本语法 在项目的 `build.gradle` 文件中,您需要在 `buildscript` 块的 `dependencies` 中添加插件依赖。基本语法如下: ```groovy buildscript { repositories { // 必须添加 google() 仓库,因为 Android Gradle 插件托管在 Google Maven 仓库 google() jcenter() // 可选,但建议保留以兼容其他库 } dependencies { classpath &#39;com.android.tools.build:gradle:3.0.1&#39; // 这是您指定的插件版本 } } ``` - **为什么需要 `google()`?** Android Gradle 插件从 3.x 版本开始,主要托管在 Google Maven 仓库(而非 jcenter)。如果缺失 `google()`,构建系统可能无法下载插件,导致 "Could not find" 错误。例如,在引用中,开发者通过添加 `google()` 解决了问题[^2][^4]。 #### 步骤 2: 完整配置示例 以下是一个完整的 `build.gradle` 文件示例(基于您提供的版本 3.0.1),确保所有模块都能正确识别插件: ```groovy // 根目录下的 build.gradle buildscript { repositories { google() // 关键:优先添加 Google 仓库 jcenter() // 备用仓库 } dependencies { classpath &#39;com.android.tools.build:gradle:3.0.1&#39; // 指定插件版本 // 其他依赖(可选),例如 classpath &#39;org.apache.httpcomponents:httpclient:4.5.4&#39; } } allprojects { repositories { google() // 确保所有模块都使用 Google 仓库 jcenter() } } ``` - **常见错误处理**: - 如果仍遇到 "Could not find" 错误,请检查网络连接或本地缓存。运行 `./gradlew clean build --refresh-dependencies` 强制刷新依赖(参考引用[^5])。 - 在 Jenkins 或 CI 环境中,确保缓存路径正确(如引用[^3] 所示,本地缓存可能已存在文件,但仓库配置错误会导致构建失败)。 - 如果使用 Gradle 4.1 或更高版本(如引用[^1] 中提到的 `gradle-4.1-all.zip`),请确认插件版本兼容性:Android Gradle 插件 3.0.1 支持 Gradle 4.1,但建议测试构建稳定性。 #### 步骤 3: 验证配置 1. 同步 Gradle:在 Android Studio 中点击 "Sync Project with Gradle Files"。 2. 检查输出日志:如果没有错误,说明配置成功。 3. 如果失败:运行命令行 `gradlew build --stacktrace` 查看详细错误(参考引用[^5]),通常问题出在仓库顺序或网络限制。 通过以上步骤,您应该能成功应用 Android Gradle 插件 3.0.1。如果问题持续,请提供更多上下文(如完整错误日志),我将进一步协助排查。 ### 相关问题 1. 如何解决 Android Gradle 插件版本不兼容的问题? 2. Android Gradle 插件 3.0.1 与 Gradle 版本有何对应关系? 3. 在 CI/CD 环境中如何优化 Android Gradle 构建速度? [^1]: 引用[1]:android 3.1 gradle,AS 3.1.3 gradle-4.1-all.zip找不到com.android.tools.build:gradle:3.0.1。找不到com.android.tools.build:gradle:3.0.1 . 在以下位置搜索:https://jcenter.bintray.com/com/android/tools/build/gradle/3.0.1/gradle-3.0.1.pom https://jcenter.bintray.com/com/android /tools/build/gradle/3.0.1/gradle-3.0.1.jar必需者:project:thirdLibrary:WordPressEditor。 [^2]: 引用[2]:Could not find com.android.tools.build:gradle:3.0.1。在build.gradle文件中的buildscript的repositories添加google( ),代码如下: buildscript { repositories { google() jcenter() } dependencies { classpath &#39;com.android.tools.build:gradle:3.1.3&#39; // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } 第三步。 [^3]: 引用[3]:Could not resolve com.android.tools.build:gradle:3.0.1。jenkins@690649a9f262:~$ find ./ -name gradle-3.0.1.pom ./.gradle/caches/modules-2/files-2.1/com.android.tools.build/gradle/3.0.1/f6941bdcc20f1efd54b55db56be1085bbe24e554/gradle-3.0.1.pom。 [^4]: 引用[4]:Error:Could not find com.android.tools.build:gradle:3.0.1.。感觉跟之前学MVP时遇到的问题类似,都应该是gradle4之后的问题,在build.gradle中的jcenter()上一行加上google()就好了 buildscript { repositories { google() jcenter() } dependencies { classpath &#39;com.android.tools.build:gradle:3.0.1&#39; classpath &#39;org.apache.httpcomponents:httpclient:4.5.4&#39; } } allprojects { repositories { google() jcenter() } }。 [^5]: 引用[5]:Could not find com.android.tools.build:gradle:3.0.1.。Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值