精华内容
下载资源
问答
  • 一、拦截器简单用法 拦截器作用于单个Page,即浏览器中的一个标签页。...通过给各种事件添加回调函数来实现的。 事件列表可参见:pyppeteer.page.Page.Events 常用拦截器: request:发出网络请求时触发 resp...

    https://www.cnblogs.com/dyfblog/p/10887940.html

    一、拦截器简单用法

    拦截器作用于单个Page,即浏览器中的一个标签页。每初始化一个Page都要添加一下拦截器。拦截器实际上是

    通过给各种事件添加回调函数来实现的。

    事件列表可参见:pyppeteer.page.Page.Events

    常用拦截器:

    request:发出网络请求时触发
    response:收到网络响应时触发
    dialog:页面有弹窗时触发
    使用request拦截器修改请求:

    复制代码
    # coding:utf8
    import asyncio
    from pyppeteer import launch

    from pyppeteer.network_manager import Request


    launch_args = {
        "headless": False,
        "args": [
            "--start-maximized",
            "--no-sandbox",
            "--disable-infobars",
            "--ignore-certificate-errors",
            "--log-level=3",
            "--enable-extensions",
            "--window-size=1920,1080",
            "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36",
        ],
    }


    async def modify_url(request: Request):
        if request.url == "https://www.baidu.com/":
            await request.continue_({"url": "https://www.baidu.com/s?wd=ip&ie=utf-8"})
        else:
            await request.continue_()


    async def interception_test():
        # 启动浏览器
        browser = await launch(**launch_args)
        # 新建标签页
        page = await browser.newPage()
        # 设置页面打开超时时间
        page.setDefaultNavigationTimeout(10 * 1000)
        # 设置窗口大小
        await page.setViewport({"width": 1920, "height": 1040})

        # 启用拦截器
        await page.setRequestInterception(True)

        # 设置拦截器
        # 1. 修改请求的url
        if 1:
            page.on("request", modify_url)
            await page.goto("https://www.baidu.com")

        await asyncio.sleep(10)

        # 关闭浏览器
        await page.close()
        await browser.close()
        return


    if __name__ == "__main__":
        loop = asyncio.get_event_loop()
        loop.run_until_complete(interception_test())
    复制代码
    使用response拦截器获取某个请求的响应:

    复制代码
    async def get_content(response: Response):
        """
            # 注意这里不需要设置 page.setRequestInterception(True)
            page.on("response", get_content)
        :param response:
        :return:
        """
        if response.url == "https://www.baidu.com/":
            content = await response.text()
            title = re.search(b"<title>(.*?)</title>", content)
            print(title.group(1))
    复制代码
    干掉页面所有弹窗:

    复制代码
    async def handle_dialog(dialog: Dialog):
        """
            page.on("dialog", get_content)
        :param dialog: 
        :return: 
        """
        await dialog.dismiss()
    复制代码
     

    二、拦截器实现切换代理

    一般情况下浏览器添加代理的方法为设置启动参数:

    --proxy-server=http://user:password@ip:port

    例如:

    复制代码
    launch_args = {
        "headless": False,
        "args": [
            "--proxy-server=http://localhost:1080",
            "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36",
        ],
    }
    复制代码
    但此种方式的缺点很明显,只能在浏览器启动时设置。当需要切换代理时,只能重启浏览器,这个代价

    就太高了,所以我们可以想想其他办法。

    思路很简单:

    request拦截器可以修改请求属性并且返回自定义响应内容
    使用第三方库来发送网络请求,并设置代理。然后封装响应内容返回给浏览器
    上代码:

    复制代码
    import aiohttp

    aiohttp_session = aiohttp.ClientSession(loop=asyncio.get_event_loop())

    proxy = "http://127.0.0.1:1080"
    async def use_proxy_base(request: Request):
        """
            # 启用拦截器
            await page.setRequestInterception(True)
            page.on("request", use_proxy_base)
        :param request:
        :return:
        """
        # 构造请求并添加代理
        req = {
            "headers": request.headers,
            "data": request.postData,
            "proxy": proxy,  # 使用全局变量 则可随意切换
            "timeout": 5,
            "ssl": False,
        }
        try:
            # 使用第三方库获取响应
            async with aiohttp_session.request(
                method=request.method, url=request.url, **req
            ) as response:
                body = await response.read()
        except Exception as e:
            await request.abort()
            return

        # 数据返回给浏览器
        resp = {"body": body, "headers": response.headers, "status": response.status}
        await request.respond(resp)
        return
    复制代码
     或者再增加一些缓存来节约一下带宽:

    复制代码
    # 静态资源缓存
    static_cache = {}

    async def use_proxy_and_cache(request: Request):
        """
            # 启用拦截器
            await page.setRequestInterception(True)
            page.on("request", use_proxy_base)
        :param request:
        :return:
        """
        global static_cache
        if request.url not in static_cache:
            # 构造请求并添加代理
            req = {
                "headers": request.headers,
                "data": request.postData,
                "proxy": proxy,  # 使用全局变量 则可随意切换
                "timeout": 5,
                "ssl": False,
            }
            try:
                # 使用第三方库获取响应
                async with aiohttp_session.request(
                    method=request.method, url=request.url, **req
                ) as response:
                    body = await response.read()
            except Exception as e:
                await request.abort()
                return

            # 数据返回给浏览器
            resp = {"body": body, "headers": response.headers, "status": response.status}
            # 判断数据类型 如果是静态文件则缓存起来
            content_type = response.headers.get("Content-Type")
            if content_type and ("javascript" in content_type or "/css" in content_type):
                static_cache[request.url] = resp
        else:
            resp = static_cache[request.url]

        await request.respond(resp)
        return
    复制代码
     

    三、反反爬虫 

    使用pyppeteer来模拟浏览器进行爬虫行动,我们的本意是伪装自己,让目标网站认为我是一个真实的人,然而

    总有一些很蛋疼的东西会暴露自己。比如当你使用我上面的配置去模拟淘宝登录的时候,会发现怎么都登录不上。因

    为浏览器的navigator.webdriver属性暴露了你的身份。在正常浏览器中,这个属性是没有的。但是当你使用pyppeteer

    或者selenium时,默认情况下这个参数就会设置为true。

    去除这个属性有两种方式。

    先说简单的,pyppeteer的启动参数中,默认会增加一个:--enable-automation

    去掉方式如下: 在导入launch之前先把默认参数改了

    from pyppeteer import launcher
    # hook  禁用 防止监测webdriver
    launcher.AUTOMATION_ARGS.remove("--enable-automation")
    from pyppeteer import launch
    还有个稍微复杂点的方式,就是利用拦截器来实现注入JS代码。

    JS代码参见:

      https://github.com/dytttf/little_spider/blob/master/pyppeteer/pass_webdriver.js

    拦截器代码:

    复制代码
    async def pass_webdriver(request: Request):
        """
            # 启用拦截器
            await page.setRequestInterception(True)
            page.on("request", use_proxy_base)
        :param request:
        :return:
        """
        # 构造请求并添加代理
        req = {
            "headers": request.headers,
            "data": request.postData,
            "proxy": proxy,  # 使用全局变量 则可随意切换
            "timeout": 5,
            "ssl": False,
        }
        try:
            # 使用第三方库获取响应
            async with aiohttp_session.request(
                method=request.method, url=request.url, **req
            ) as response:
                body = await response.read()
        except Exception as e:
            await request.abort()
            return

        if request.url == "https://www.baidu.com/":
            with open("pass_webdriver.js") as f:
                js = f.read()
            # 在html源码头部添加js代码 修改navigator属性
            body = body.replace(b"<title>", b"<script>%s</script><title>" % js.encode())

        # 数据返回给浏览器
        resp = {"body": body, "headers": response.headers, "status": response.status}
        await request.respond(resp)
        return
    复制代码
    这个功能pyppeteer是有专门的函数来做这件事情的:

    pyppeteer.page.Page.evaluateOnNewDocument

    BUT,这个函数实现的有问题,总是不起作用 。而与之对比,如果你用的是nodejs的puppeteer的话,这个函数

    是生效的。 

     

    四、使用Xvfb配合实现headless效果

    之所以用pyppeteer,很大程度上是为了使用chromium的无头headless模式。无头更省资源,限制也少。然而现

    实很残酷,特别是对爬虫。

    类似于navigator.webdriver这样的东西可以用来检测是否是机器人。还有更多的手段可以来检测是否是headless。

    比如:headless模式下没有window.chrome属性。具体我就不列了,反正好多。可以参见文后链接。关于如何伪装

    headless模式,使其不被探测到,网上资料也有很多,也很有用。但是,这个东西细节太多了。。。。。。还得看目

    标网站工程师的心情和实力。如果对方有大把时间去检测各种边边角角的东西,不断提升代码的混淆程度,死磕到底

    的话,就有点得不偿失了。

    于是,我在死磕了携程酒店三天后,幡然醒悟。(有兴趣的可以尝试一下,看如何在无头模式下爬取携程酒店数据)

    既然无头这么难搞,就不搞了。直接使用Xvfb来实现虚拟显示器,这样就变成有头的了:)。

    问题解决。

     

    文内Python代码见:

    https://github.com/dytttf/little_spider/blob/master/pyppeteer/use_case.py

     

    参考文献:

    无头浏览器相关

    MAKING CHROME HEADLESS UNDETECTABLE

    Detecting Chrome headless, new techniques

    Xvfb

    https://www.x.org/releases/X11R7.6/doc/man/man1/Xvfb.1.xhtml

    https://blog.csdn.net/Nobody_Wang/article/details/60887659

    https://stackoverflow.com/questions/57298901/unable-to-hide-chrome-is-being-controlled-by-automated-software-infobar-within

    展开全文
  • 心血来潮的看了两章编译原理,就现学现用,试试自己实现下类似jQuery的选择器,凡事总有个过程,循序渐进慢慢来搞,这次先做个大致的函数结构,内部细节慢慢实现。能不能实现就随缘吧~~哈哈~~  其实相对...

    心血来潮的看了两章编译原理,就现学现用,试试自己实现下类似jQuery的选择器,凡事总有个过程,循序渐进慢慢来搞,这次先做个大致的函数结构,内部细节慢慢实现。能不能实现就随缘吧~~哈哈~~


           其实相对实现一个编译器的流程来说,实现选择器已经是非常之容易的了,大部分工作集中在编译器的第一步:词法分析。而且很多工作已经被简化了,如映射为词法单元。接着做一些简单的语法分析(主要对CSS选择符分析),后面那些语义分析都不需要了。下面先简单给出些术语定义和产生式(语义规则看不懂略过也无所谓):


    1、标识符:跟在一元运算符后面的字母数字之类的(这个不用多废话应该。。)。选择器函数中标识符的合法性,由浏览器自己去搞定就行了,我们就不需要再去一个 一个字符去检测了,因此我把标识符看作无法继续分解的单元, 这里产生式简单描述为:term -> 合法的字符串


    2、运算符:包括一元运算符:类选择'.' , id选择 '#',标签选择''(标签运算为空字符);二元运算符:需要2个操作数的运算符,包括直接后代选择符'>',同胞选择符'+',后台选择符' ';属性运算符:在属性选择符号中的一些运算符,如[value^=10]中的'^=';伪类运算符:由':'开头,跟着一些伪类选择标识符;

    产生式表示为:op -> '#' | '.' | '' | ' ' | '>' | '+' 

    运算符产生式目前只写出了一元和二元运算符,属性以及伪类以后再慢慢补充


    3、表达式:能够求出值,也就是返回的是一个存储着符合条件的元素数组,若没有符合条件的元素,则为空数组,由运算符加标识符组成。

    产生式表示为:expr ->   expr1 + op + expr2   | op + term |    expr1 + op + term | '[' + term1 + op + term2 + ']' | expr1 + ':' + op | '*'


    表达式的产生式这里做些讲解:op + term 对应一元运算如#xx,   '[' + term1 + op + term2 + ']'对应属性运算,如[value=10]

    expr1 + ':' + op对应伪类运算,如a:last-chiald,   expr1 + op + expr2对应二元运算,如:div  > .main; expr1 + op + term对应组合表达式,如div.main.xx


    这里汇总一下产生式:

    term -> 合法的字符串

    op -> '#' | '.' | '' | ' ' | '>' | '+' 

    expr ->   expr1 + op + expr2   | op + term |    expr1 + op + term | '[' + term1 + op + term2 + ']' | expr1 + ':' + op | '*'

    语义分析到此完毕~~~


    展开全文
  • 线性回归理解(附纯python实现

    万次阅读 多人点赞 2017-03-27 20:44:40
    线性回归是机器学习中最基本的一个算法,但是那些所谓的效果很好的算法也无非是从这些基础算法慢慢演变而来。高中时候的数学老师给我讲过一个乔峰的故事,我今天再添油加醋的给你们说下。天龙八部中,乔峰在聚贤庄...

    线性回归是机器学习中最基本的一个算法,但是那些所谓的效果很好的算法也无非是从这些基础算法慢慢演变而来。高中时候的数学老师给我讲过一个乔峰的故事,我今天再添油加醋的给你们说下。天龙八部中,乔峰在聚贤庄大战江湖群雄这个算是经典了,当时各路武林豪杰纷纷使出自家的看门绝学,什么易筋经啊,九阴真经啊,葵花点穴手啊等等,但统统都被乔峰一拳KO,直接秒杀,竟无一人是其敌手,那乔峰用的是什么高深武学呢?其实他用的是拳法当作最为基础的一套拳法,名为长拳,有点拳法方面知识的人都知道,长拳其实一套最最基础的拳法,大家可以把它想成军训时候的匕首操。但就是这么个匕首操把这么多绝世武功都KO了。为啥?因为乔峰小时候在少林寺山脚住的时候,就开始苦练基本功,苦练长拳,并没用刻意的去最求一些更高水平的武学,这才将一套基础拳法发挥得如此淋漓尽致。

    这个故事我也只听了个大概,上面我很大部分都自己瞎写的,我就是要说一个道理。
    第一:基础很重要
    第二:一些简单的东西,学好了不比很多复杂的高深的东西差。
    说了这么多,其实就是要引出今天的主题-------线性回归。线性回归我觉得可以当成是机器学习中的长拳。

    线性回归

    线性回归包括一元线性回归和多元线性回归,一元的是只有一个x和一个y。多元的是指有多个x和一个y。
    下面我只讲下一元的,多元只是将这里写图片描述 变成了这里写图片描述

    一元线性回归其实就是去找到一条直线,这条直线能以最小的误差(Loss)来拟合数据。
    这里写图片描述

    怎么来表示误差呢?

    这里写图片描述
    如上图所示,横坐标表示x,纵坐标表示y。我们要找的就是图中的这条直线。我们要去找到这条直线,大家可以想象,我们肯定希望找到的那条线,距离每个点都很近,最好所有的点上都在这条线上,但是一条直线去拟合所有的点都在这条直线上肯定不现实,所以我们希望这些点尽量离这条直线近一点。即去找每个点和直线的距离 这里写图片描述最小的那条线,为了简单起见,将绝对值转化为平方,那么误差可以表示为这里写图片描述,这里i表示第i个数据,N表示总的样本个数。一般我们还会把Loss求和平均,来当作最终的损失,
    这里写图片描述

    怎么去最小化误差?

    我们要怎么去找到最能拟合数据的直线?即最小化误差呢?
    一般有两个方法:

    最小二乘法

    上面我们讲了我们定义的损失这里写图片描述,其中的x,y,i,N都是已知的,那么我们就可以把这个方程看作是m和b的方程。作为一个m和b的二次方程。那么求Loss最小值的问题就转变成了求极值问题,这个高数学过的都应该知道点。

    怎么求极值呢?

    令每个变量的偏导数为零,求方程组的解呗,这个是很基础的高数问题了。
    我们可以得到下面的方程组
    这里写图片描述
    这里写图片描述
    然后就是巴拉巴拉巴拉把m和b求出来,这样就得到我们要的线性方程了。

    梯度下降法

    没有梯度下降就没有现在的深度学习,这是一个神奇的算法。
    最小二乘法可以一步到位,直接算出m和b,但他是有前提的,具体我有点记不清了,好像是需要满秩什么的。梯度下降法和最小二乘不一样,它通过一步一步的迭代,慢慢的去靠近到那条最优直线。
    最小二乘法里面我们提到了两个偏导数,分别为
    这里写图片描述
    这里写图片描述
    我们要去找Loss这个方程的最小值,最小值怎么求?按数学的求法就是最小二乘法呗,但是大家可以直观的想一下,很多地方都会用一个碗来形容,那我也找个碗来解释吧。
    这里写图片描述
    大家把这个Loss函数想象成这个碗,而我们要求的最小值就是碗底。假设我们现在不能用最小二乘法求极小值,但是我们的计算机的计算能量很强,我们可以用计算量换结果,不管我们位于这个碗的什么位置,只要我们想去碗底,就要往下走。
    往下走????????
    这个下不就是往梯度方向走吗,那我们沿着梯度一点一点滑下去呗,反正计算机不嫌累。梯度不就是上面那两个公式呗。现在梯度有了,那每次滑多远呢,一滑划过头了不久白算半天了吗,所以还得定义步长,用来表示每次滑多长。这样我们就能每次向下走一点点,再定义一个迭代值用来表示滑多少次,这样我们就能慢慢的一点点的靠近最小值了,不出意外还是能距离最优值很近的。

    顺便把上面这个梯度下降法实现下

    每次向下滑要慢慢滑,就是要个步长,我们定义为learning_rate,往往很小的一个值。

    向下滑动的次数,就是迭代的次数,我定义为num_iter,相对learning_rate往往很大。

    定义好这两个,我们就可以一边求梯度,一边向下滑了。就是去更新m和b。
    这里写图片描述
    这里写图片描述
    我这里用了加号,很多人会误以为梯度下降就要减,但是其实梯度本身是有方向的,所以这里直接加就可以。

    如下图所示,我们做的初始化

    这里写图片描述

    然后就是优化器,优化器就是去做梯度下降

    这里写图片描述

    它里面的具体步骤,如下

    这里写图片描述

    里面的compute_gradient方法就是去计算梯度做参数更新

    这里写图片描述

    需要代码的可以到我github上下载。https://github.com/Shicoder/DeepLearning_Demo/tree/master/linear_regression_use_gradient_decent

    Notes:
    线性回归适用于线性变换数据
    线性回归受噪声影响较大

    展开全文
  • 使用DrawerLayout实现QQ5.0侧拉菜单效果

    千次阅读 2015-11-08 00:44:19
    在上一篇文章中,我们介绍了怎么使用DrawerLayout来实现一个简单的侧拉菜单(使用DrawerLayout实现侧拉菜单),也就是我们常说的抽屉效果,GitHub上类似效果的实现方式非常多,实现出来的效果也是非常的绚丽,但是万...

    在上一篇文章中,我们介绍了怎么使用DrawerLayout来实现一个简单的侧拉菜单(使用DrawerLayout实现侧拉菜单),也就是我们常说的抽屉效果,GitHub上类似效果的实现方式非常多,实现出来的效果也是非常的绚丽,但是万变不离其宗,Google提供给我们的DrawerLayout才是最基本的,我们今天就来介绍一下怎样通过DrawerLayout来实现QQ5.0的侧拉效果。先来看一张效果图:



    好,这是一个我们即将要实现的效果图,关于这个效果,大部分都是很简单的,都是使用了我们在上一篇博客中介绍的DrawerLayout技术来做的,所以如果你还没读过上一篇博客,建议先去看一下DrawerLayout的使用方法(使用DrawerLayout实现侧拉菜单),了解了DrawerLayout的使用方法之后,那么要实现QQ5.0的侧拉效果就如同探囊取物一般。

    好了,废话不多说,我们就先来看看怎么实现这样一个效果。

    先来看看主布局文件:

    <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/drawerLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/background" >
    
        <RelativeLayout
            android:id="@+id/mContent"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
    
            <ImageView
                android:id="@+id/mContent_iv"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_margin="0dp"
                android:background="@drawable/content_iv"
                android:padding="0dp" />
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@null"
                android:onClick="onClick" />
        </RelativeLayout>
    
        <fragment
            android:id="@+id/left_fragment"
            android:name="com.lenve.qqdrawerlayout.LeftMenuFragment"
            android:layout_width="220dp"
            android:layout_height="match_parent"
            android:layout_gravity="left"
            android:tag="LEFTMENU" />
    
    </android.support.v4.widget.DrawerLayout>



    整个布局文件还是比较简单的,DrawerLayout中一共分为两大块,第一块就是主布局文件,先是是一张图片(这里我直接截了QQ页面的一张图),然后就是一个Button,注意这个Button的位置在整个页面的左上角,Button的背景设置为null,所以我们看不到Button,但是这个Button会响应我们的点击事件,第二部分是一个Fragment,这个Fragment我们看android:layout_gravity="left"属性就知道它是左边菜单栏的Fragment,在上一篇博客中,左边的菜单栏我们并没有使用Fragment,而是直接使用了布局文件,其实这里写成Fragment是比较好的,方便扩展,也方便管理。

    好了,既然左边是一个Fragment,我们就来看看这个Fragment长什么样子?

    先看看Fragment的布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <ListView
            android:id="@+id/left_menu_lv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="150dp" >
        </ListView>
    
    </RelativeLayout>

    就一个ListView,够简单吧,再看看listview中item的布局文件:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <ImageView
            android:id="@+id/left_iv"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_marginLeft="20dp"
            android:padding="12dp"
            android:src="@drawable/svip" />
    
        <TextView
            android:id="@+id/left_tv"
            android:layout_width="wrap_content"
            android:layout_height="48dp"
            android:layout_marginLeft="20dp"
            android:layout_toRightOf="@id/left_iv"
            android:gravity="center"
            android:text="开通会员"
            android:textColor="@android:color/white"
            android:textSize="14sp" />
    
    </RelativeLayout>

    item的布局文件也是比较简单的,左边一个ImageView,右边一个TextView,我们再看看Fragment:

    public class LeftMenuFragment extends Fragment {
    
    	private List<LeftMenu> list = null;
    	private ListView lv;
    
    	@Override
    	public void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		initData();
    	}
    
    	/**
    	 * 初始化数据
    	 */
    	private void initData() {
    		list = new ArrayList<LeftMenu>();
    		list.add(new LeftMenu(R.drawable.svip, "开通会员"));
    		list.add(new LeftMenu(R.drawable.qianbao, "QQ钱包"));
    		list.add(new LeftMenu(R.drawable.zhuangban, "个性装扮"));
    		list.add(new LeftMenu(R.drawable.shoucang, "我的收藏"));
    		list.add(new LeftMenu(R.drawable.xiangce, "我的相册"));
    		list.add(new LeftMenu(R.drawable.wenjian, "我的文件"));
    	}
    
    	@Override
    	public View onCreateView(LayoutInflater inflater, ViewGroup container,
    			Bundle savedInstanceState) {
    		View view = inflater.inflate(R.layout.left_menu, container, false);
    		lv = (ListView) view.findViewById(R.id.left_menu_lv);
    		LeftMenuAdapter adapter = new LeftMenuAdapter(list);
    		lv.setAdapter(adapter);
    		return view;
    	}
    }

    Fragment整体也是比较简单的,在onCreate方法中拿到模拟数据,然后在onCreateView方法中给listview设置Adapter,在这里我们使用了一个JavaBean,这个JavaBean主要用来存储每一个item中的数据:

    public class LeftMenu {
    
    	private int imageView;
    	private String text;
    
    	public int getImageView() {
    		return imageView;
    	}
    
    	public void setImageView(int imageView) {
    		this.imageView = imageView;
    	}
    
    	public String getText() {
    		return text;
    	}
    
    	public void setText(String text) {
    		this.text = text;
    	}
    
    	public LeftMenu(int imageView, String text) {
    		this.imageView = imageView;
    		this.text = text;
    	}
    
    	public LeftMenu() {
    	}
    
    }

    在这个JavaBean中,我们存储Image的资源id,然后在使用的时候直接调用这个资源id即可,再看看Adapter:

    public class LeftMenuAdapter extends BaseAdapter {
    
    	private List<LeftMenu> list;
    
    	public LeftMenuAdapter(List<LeftMenu> list) {
    		this.list = list;
    	}
    
    	@Override
    	public int getCount() {
    		return list.size();
    	}
    
    	@Override
    	public Object getItem(int position) {
    		return list.get(position);
    	}
    
    	@Override
    	public long getItemId(int position) {
    		return position;
    	}
    
    	@Override
    	public View getView(int position, View convertView, ViewGroup parent) {
    		ViewHolder holder = null;
    		if (convertView == null) {
    			convertView = LayoutInflater.from(parent.getContext()).inflate(
    					R.layout.listview_item, null);
    			holder = new ViewHolder();
    			holder.iv = (ImageView) convertView.findViewById(R.id.left_iv);
    			holder.tv = (TextView) convertView.findViewById(R.id.left_tv);
    			convertView.setTag(holder);
    		} else {
    			holder = (ViewHolder) convertView.getTag();
    		}
    		holder.iv.setImageResource(list.get(position).getImageView());
    		holder.tv.setText(list.get(position).getText());
    		return convertView;
    	}
    
    	class ViewHolder {
    		ImageView iv;
    		TextView tv;
    	}
    }


    这个Adapter很简单,我就不多说了。

    nineoldandroids

    这些都看完了,就该说说我们的MainActivity,首先我们得大概说说这种效果是怎么实现的,说到这里,我就得给大家介绍一个非常非常出名的动画类了,那就是nineoldandroids,我们在GitHub上可以直接下载到这个动画类的源码(https://github.com/JakeWharton/NineOldAndroids),如果你想用jar包,一会直接下载本项目源码就能拿到了。

    nineoldandroids中有一些非常好用的方法,比如:

    ViewHelper.setScaleX(View view, float scaleX)
    ViewHelper.setScaleY(View view, float scaleY)

    通过这个方法,我们可以对一个View进行缩放,第一个参数是我们要缩放的View,第二个参数是缩放比例,还有一个方法:

    ViewHelper.setAlpha(View view, float alpha)
    通过这个方法,我们可以设置一个View的透明度,第一个参数是我们要改变透明度的View,第二个参数是透明度。还有一个方法:

    ViewHelper.setTranslationX(View view, float translationX)
    通过这个方法,我们可以对一个View进行平移操作,第一个参数是要平移的View,第二参数是在X轴平移多少。还有一个方法:

    ViewHelper.setScaleX(View view, float scaleX)
    ViewHelper.setScaleY(View view, float scaleY)

    通过这个方法,我们可以设置View变化时的一个轴心。

    为什么介绍这几个方法呢?原因很简单,因为我们即将用到。

    好了,介绍完这几个方法之后,我们的MainActivity就可以登场了。只需要简单的几个动画我们就可以实现QQ的这种侧拉效果了。


    在MainActivity中我们先拿到DrawerLayout的一个实例:

    drawerLayout = (DrawerLayout) this.findViewById(R.id.drawerLayout);
    然后我们要给DrawerLayout设置一个监听事件,当菜单出场的时候我们调整主布局的位置:

    		drawerLayout.setDrawerListener(new DrawerListener() {
    			@Override
    			public void onDrawerStateChanged(int newState) {
    				Log.i("lenve", "onDrawerStateChanged");
    			}
    
    			@Override
    			public void onDrawerSlide(View drawerView, float slideOffset) {
    				slideAnim(drawerView, slideOffset);
    				Log.i("lenve", "onDrawerSlide");
    			}
    
    			@Override
    			public void onDrawerOpened(View drawerView) {
    				Log.i("lenve", "onDrawerOpened");
    			}
    
    			@Override
    			public void onDrawerClosed(View drawerView) {
    				Log.i("lenve", "onDrawerClosed");
    			}
    		});

    这个监听事件一共要实现其中的四个方法,看名字我们大概也知道这四个方法是干什么的,那我们看看这四个方法的执行时机:

    当我们打开菜单的时候,先执行onDrawerStateChanged,然后不断执行onDrawerSlide,第三步会执行onDrawerOpened,最后执行onDrawerStateChanged,当我们关闭菜单的时候,先执行onDrawerStateChanged,然后不断执行onDrawerSlide,第三步会执行onDrawerClosed,最后执行onDrawerStateChanged,好了,方法的执行时机如果大家还有疑问可以通过打印日志来查看各个方法的执行时机。好了,我们的动画逻辑要在onDrawerSlide方法中来完成,先来说说这个方法的这两个参数,第一个参数是一个View,这个View其实就是左边侧拉菜单的View,第二参数是偏移量,可以简单理解为左边菜单拉出来的比例,它的取值是从0到1。介绍完这个之后,我们来看看slideAnim(drawerView, slideOffset);方法,看看最核心的代码有多么简单:

    	private void slideAnim(View drawerView, float slideOffset) {
    		View contentView = drawerLayout.getChildAt(0);
    		// slideOffset表示菜单项滑出来的比例,打开菜单时取值为0->1,关闭菜单时取值为1->0
    		float scale = 1 - slideOffset;
    		float rightScale = 0.8f + scale * 0.2f;
    		float leftScale = 1 - 0.3f * scale;
    
    		ViewHelper.setScaleX(drawerView, leftScale);
    		ViewHelper.setScaleY(drawerView, leftScale);
    		ViewHelper.setAlpha(drawerView, 0.6f + 0.4f * (1 - scale));
    		ViewHelper.setTranslationX(contentView, drawerView.getMeasuredWidth()
    				* (1 - scale));
    		ViewHelper.setPivotX(contentView, 0);
    		ViewHelper.setPivotY(contentView, contentView.getMeasuredHeight() / 2);
    		contentView.invalidate();
    		ViewHelper.setScaleX(contentView, rightScale);
    		ViewHelper.setScaleY(contentView, rightScale);
    	}
    好了,我们可以看到rightScale取值是从1到0.8,那么大家注意rightScale最后用在了contentView上,所以对应的一个显示效果就是contentView从最初大小变为最初大小的0.8倍,leftScale取值是从0.7到1。leftScale最后用在了drawerView,也就是左边的侧拉菜单View,那么对应的显示效果就是左边的菜单View一开始只有原本布局的0.7倍,在菜单慢慢往出滑动的时候,它逐渐变大,直到变为原本的大小(这个时候菜单View已经完全显示出来了)。另外几个动画设置都比较简单,大家参照我们上面对这几个方法的讲解来理解。

    好了,搞定这些之后,还剩最后一个东东,就是左上角的点击事件,这个我们在上一篇博客中已经介绍过了,这里我就直接贴代码:

    	public void onClick(View v) {
    		drawerLayout.openDrawer(Gravity.LEFT);
    	}


    所有这些工作做完之后,一个仿QQ5.0侧拉的Demo已经出炉了。。。。


    好了,今天就说到这里,大家有什么问题,可以留言讨论。


    Demo下载https://github.com/lenve/QQDrawerLayout


    展开全文
  • Android 自定义ViewGroup手把手教你实现ArcMenu

    万次阅读 多人点赞 2014-07-08 17:41:52
    逛eoe发现这样的UI效果,感觉很不错,后来知道github上有这么个开源项目~~~~当然本篇不是教你如何...1、实现思路 通过效果图,会有几个问题: a、动画效果如何实现 可以看出动画是从顶点外外发射的,可能有人说,那还
  • Android ListView功能扩展,实现高性能的瀑布流布局

    万次阅读 多人点赞 2015-10-08 09:11:01
    一直关注我博客的朋友们应该知道,其实在很早之前我就发布过一篇关于实现瀑布流布局的文章,Android瀑布流照片墙实现,体验不规则排列的美感。但是这篇文章中使用的实现算法比较简单,其实就是在
  • 别混淆你想要什么和能否实现

    千次阅读 多人点赞 2017-06-07 06:51:26
    你真的想要实现你的某个愿望吗?未必!
  • markdown如何实现锚点功能

    千次阅读 2020-01-07 00:48:59
    最近慢慢开始写一点博客,写完一篇后发现排版上少了目录,感觉用起来不方便,那么怎么在markdown中实现目录与跳转呢?用锚点啊,嘿嘿~
  • tensorflow实现基于LSTM的文本分类方法

    万次阅读 多人点赞 2016-11-25 14:47:13
    鄙人接触tensor flow的时间不长,也是在慢慢摸索,但是因为有之前使用Theano的经验,对于符号化编程也不算陌生,因此上手Tensorflow倒也容易。但是感觉tensorflow还是和theano有着很多不一样的地方,这里也会提及...
  • RecyclerView探索之通过ItemDecoration实现StickyHeader效果

    万次阅读 多人点赞 2017-04-17 15:58:42
    我在上一篇《小甜点,RecyclerView 之 ItemDecoration 讲解及高级特性实践 》 讲解了 ItemDecoration 的基本用法及它的一些实践,抱着学习研究的态度,这一篇作为实践篇主要目的是尝试通过 ItemDecoration 来实现 ...
  • JS实现日历控件选择后自动填充

    千次阅读 多人点赞 2013-07-20 10:39:08
    JS实现日历控件选择后自动填充
  • 上次说过在看一些关于0代码开发平台ivx,前一段时间忙完考试最近跟着教程0代码实现一个九宫格抽奖,哈哈哈感觉还是蛮强大的,懂点的人都知道可视化这个东西我们正常都是用一些包或者库来实现数据可视化。而可视化...
  • 十大经典排序算法详细总结(含JAVA代码实现)

    万次阅读 多人点赞 2018-11-06 12:19:45
    文章目录十大经典排序算法详细总结(含JAVA代码实现)0、排序算法说明1、冒泡排序(Bubble Sort)2、选择排序(Selection Sort)3、插入排序(Insertion Sort)4、希尔排序(Shell Sort)5、归并排序(Merge Sort)6、...
  • 小秋今天去面试了,面试官问了一个与敏感词过滤算法相关的问题,然而小秋对敏感词过滤算法一点也没听说过。于是,有了下下事情的发生… 面试官开怼 面试官:玩过王者荣耀吧?了解过敏感词过滤吗?,例如在游戏里,...
  • 利用URL重写实现参数目录化

    万次阅读 热门讨论 2013-07-09 18:34:25
    参数目录化,就是将 类似 http://www.abc.com/store/store.aspx?id=1024 这样...要实现这种功能,可以用以下三种方式:微软的URL重写模块2.0、isapi_rewrite、urlrewriter.net。用法大同小异,关键在于写对正则表达式。
  • 面试字节跳动的一点小经验

    万次阅读 2019-07-16 23:30:21
    为的就是以后可以过上早上八点半起床,然后慢慢悠悠走到公司还不迟到(可能还是很早来的人之一)的生活。 当然,这是我为什么想去字节跳动的原因。换算到你们自己的时候,你们也要想一想是因为什么想要换一份工作、...
  • qt 实现停靠窗口 效果

    千次阅读 2015-06-25 13:43:10
    我创建了一个class继承QWidget,实现enterEvent和leaveEvent两个函数,我实现的效果是这样,一开始这个窗口显示出来,但只显示一半,就是move(-width/2),一旦有鼠标进入的消息,就起个timeline去一点一点儿的向右...
  • 前言:这个月真是过得太快了,本来说要看四章的内容,这下才看了两章,擦……严重没完成预算啊……撞豆腐死了算了 相关文章: 1、《ListView滑动删除...3、《ListView滑动删除实现之三——创建可滑动删除的Li...
  • 之所以称之为漫水填充,是因为这种算法就是模拟了涨水的过程,从一点开始,水流慢慢加大,直到漫过了全部区域。 这个算法的详细介绍可以参考下面的链接。 https://en.wikipedia.org/wiki/Flood_fill这个算法在我们...
  • 妙用AccessibilityService黑科技实现微信自动加好友拉人进群聊 标签: 2018 引言: 在上上周的周六和周日,我发了两篇利用itchat实现微信机器人的文章(Python): 小猪的Python学习之旅 —— 18.Python微信...
  • 简介这是一篇没有什么实际作用的文章,因为没有任何shader效果实现,整篇文章到最后,我只实现了一个旋转的立方体(o(╯□╰)o),和Unity渲染的万紫千红的3D世界显得有很大落差,仿佛一切都回到了最初的起点。...
  • 当然,还想更秀操作一点,我们在第一和二层循环那里,使用位移运算,来完成与运算,本质是一样的,代码如下 public static List<List<Integer>> binaryBit(int[] nums) { List<List<Integer>> res = new ...
  • Linux环境下模拟实现命令解释器

    万次阅读 2012-12-17 21:10:01
    在Linux环境下模拟实现命令解释器 一.程序概述 1. 进入系统概述 本次课程设计是在红帽Linux发行版(Red Hat Enterprise Linux AS release 4 (Nahant Update 4) ))环境下运行,内核为:Kernel 2.6.9-42.ELsmp ...
  • Android应用开发--MP3音乐播放器代码实现(二)

    万次阅读 多人点赞 2013-05-26 08:11:49
    Android应用开发--MP3音乐播放器代码实现(二)   2013年5月25日 简、美音乐播放器开发 小巫在这里罗列这个播放器已经实现的功能: 1. 自动显示音乐列表 2. 点击列表播放音乐 3. 长按列表弹出对话框 4. ...
  • UE4移动组件详解(一)——移动框架与实现原理

    万次阅读 多人点赞 2017-11-29 20:44:29
    分别从移动框架与实现原理,移动的网络同步,移动组件的优化与改造三个方面来写。这三篇文档中难免有问题和漏洞,所以我也会在发现问题时及时更新和修改,也希望大家能给出一些建议。 一.深刻理解移动组件的意义...
  • 这书写的简单易懂,每天花一点时间学习一小节,慢慢来。 这一节学的是用 修改UV值来实现纹理贴图的滚动,标题太长,其实就是让一个图片在滚来滚去,来模拟一些水流效果之类的。 这个效果其实在逻辑代码中也可以写...
  • 用单片机实现流水灯(进阶版)

    千次阅读 多人点赞 2019-12-06 21:29:25
    ” 别急,慢慢看嘛,就算是简单的跑马灯,也可以玩出花样的哟。 好吧,不卖关子了,先说说我今天讲的内容吧,首先:如何用P1口(只有八个引脚哟)实现八个流水灯,然后:如何用P1口实现十六个流水灯。怎么样,有没有...
  • NSGA3算法及其MATLAB版本实现

    万次阅读 多人点赞 2018-01-02 18:34:02
    NSGA3算法及其MATLAB版本实现 一丶NSGA3和NSGA2的一些参考资料 看懂NSGA3之前,了解的NSGA2的话更有帮助,这个博士写的带约束的NSGA2的matlab版本很不错(9个非约束的测试问题和5个带约束的测试问题),大家想...
  • 用php实现正方教务系统的模拟登陆

    千次阅读 2016-03-27 13:52:04
    第一次用php实现模拟登陆真的是弄了好久,还挺好玩的就是,都是自己一点一点探索的收获还是慢慢的。到现在还有一点没弄明白的就是为什么不用提交验证码信息也能登陆,感觉可能是bug吧! 想要实现模拟登陆,首先要...
  • Android应用开发-MP3音乐播放器代码实现(三)

    万次阅读 多人点赞 2013-05-29 21:10:32
    Android应用开发-MP3音乐播放器代码实现(三) 2013年5月29 简、美音乐播放器开发记录 这篇博客是接着上一篇博客的,点击列表会进入播放的Activity,在这个Activity会接收到从前面的Activity传来的数据,在通过这些...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 117,851
精华内容 47,140
关键字:

一点一点慢慢实现