皮皮网

【波段龙回头指标源码】【枪神纪源码格式】【安卓源码解释】android view源码

时间:2024-11-14 23:50:38 来源:hbuilderx小程序源码 作者:协成无线认证源码

1.android viewԴ??
2.Android 自定义View:为什么你设置的wrap_content不起作用?
3.Android UI绘制之View绘制的工作原理
4.android的自定义View的实现原理?哪位能给我个思路呢。谢谢。
5.android自定义view
6.Android自定义view实现圆环效果详解

android view源码

android viewԴ??

       自定义View是Android开发中的关键技能。通过自定义控件,开发者能够实现独特且满足特定需求的用户界面。在深入探讨自定义View的波段龙回头指标源码知识点之前,首先需要了解Android的控件架构。Android的控件分为View和ViewGroup两大类。ViewGroup可以包含多个View,负责管理它们的绘制、测量和交互。View树的构建是通过深度遍历的方法在Activity中使用findViewById()完成,顶部的ViewParent对象是控件树的核心,负责调度交互事件。Activity加载布局文件时,使用setContentView()方法,将顶级View初始化并开始加载过程。View的绘制从Activity的onCreate方法开始,当View树发生变化或主动调用invalidate方法时,会引发绘制。枪神纪源码格式

       当通过setContentView方法加载顶级View后,紧接着Activity的生命周期会执行到onResume方法,此时DecorView由Window对象初始化,View的工作流程从DecorView开始。View的测量和大小确定通过measure方法实现,根据View和ViewGroup的特性,measure方法分为测试自身大小和测量子View大小两部分。MeasureSpec作为测量过程中的规格信息,包含了尺寸和测量模式。在生成MeasureSpec时,考虑父布局、LayoutParams以及padding和margin等因素。UNSPECIFIED用于在父View不限制子View宽高时的场景,如ScrollView。

       自定义View,如FlowLayout,需要解决大小设定、内部子View布局和UI元素绘制三个问题。onMeasure方法负责测量大小,安卓源码解释onLayout方法定义子View的显示规则,onDraw方法用于根据相应属性将UI元素绘制到界面。在自定义FlowLayout中,onMeasure方法通过递归测量子View并计算总高度来确定最终大小,onLayout方法则遍历子View并为每个子View设置布局位置。

       在面试中遇到的问题涉及到Activity内的根布局LinearLayout与内部包含的View的背景颜色和宽高的关系。理解自定义View的measure过程后,可以轻松解答此类问题。LinearLayout的onMeasure方法基于父View的MeasureSpec计算子View的测量规格,进而调用子View的measure方法。最终,子View的宽度和高度确定为父View的宽高,即屏幕宽高,实现全屏显示蓝色背景。

       掌握自定义View的知识点,包括测量、布局和绘制,能够极大地提升Android开发者的灵活性和创造力,实现更为复杂和个性化的代刷网站 源码用户界面。随着实践经验的积累,开发者将能更加熟练地运用这些知识点,构建出丰富多样的应用。

Android 自定义View:为什么你设置的wrap_content不起作用?

        在使用自定义View时,View宽 / 高的 wrap_content 属性不起自身应有的作用,而且是起到与 match_parent 相同作用。

        其实这里有两个问题:

        请分析 & 解决问题之前,请先看自定义View原理中 (2)自定义View Measure过程 - 最易懂的自定义View原理系列

        问题出现在View的宽 / 高设置,那我们直接来看自定义View绘制中第一步对View宽 / 高设置的过程:measure过程中的 onMeasure() 方法

        继续往下看 getDefaultSize()

        从上面发现:

        那么有人会问:wrap_content和match_parent具有相同的效果,为什么是填充父容器的效果呢?

        我们知道,子View的MeasureSpec值是根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来,具体计算逻辑封装在getChildMeasureSpec()里。

        接下来,我们看生成子View MeasureSpec的方法: getChildMeasureSpec() 的源码分析:

        getChildMeasureSpec()

        从上面可以看出,当子View的布局参数使用 match_parent 或 wrap_content 时:

        所以: wrap_content 起到了和 match_parent 相同的作用:等于父容器当前剩余空间大小

        当自定义View的布局参数设置成wrap_content时时,指定一个默认大小(宽 / 高)。

        这样,当你的自定义View的宽 / 高设置成wrap_content属性时就会生效了。

        网上流传着这么一个解决方案:

        答:是,当父View为 AT_MOST 、View为 match_parent 时,该View的 match_parent 的效果就等于 wrap_content 。上述方法存在逻辑错误,但由于这种情况非常特殊的,所以导致最终的结果没有错误。具体分析请看下面例子:

        从上面的效果可以看出,View大小 = 默认值

        我再将子View的属性改为 wrap_content :

        从上面的效果可以看出,View大小还是等于默认值。

        相信看到这里你已经看懂了:

        为了更好的表示判断逻辑,我建议你们用本文提供的解决方案,即根据布局参数判断默认值的设置

        不定期分享关于安卓开发的干货,追求短、平、快,但却不缺深度。

Android UI绘制之View绘制的工作原理

        这是AndroidUI绘制流程分析的第二篇文章,主要分析界面中View是如何绘制到界面上的具体过程。

        ViewRoot 对应于 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View的三大流程均是通过 ViewRoot 来完成的。在 ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRootImpl 对象,并将 ViewRootImpl 对象和 DecorView 建立关联。

        measure 过程决定了 View 的宽/高, Measure 完成以后,可以通过 getMeasuredWidth 和 getMeasuredHeight 方法来获取 View 测量后的宽/高,在几乎所有的情况下,它等同于View的最终的宽/高,但是特殊情况除外。 Layout 过程决定了 View 的四个顶点的坐标和实际的宽/高,完成以后,可以通过 getTop、getBottom、getLeft 和 getRight 来拿到View的四个顶点的位置,可以通过 getWidth 和 getHeight 方法拿到View的最终宽/高。 Draw 过程决定了 View 的显示,只有 draw 方法完成后 View 的内容才能呈现在屏幕上。

        DecorView 作为顶级 View ,一般情况下,它内部会包含一个竖直方向的 LinearLayout ,在这个 LinearLayout 里面有上下两个部分,上面是标题栏,下面是内容栏。在Activity中,我们通过 setContentView 所设置的布局文件其实就是被加到内容栏中的,而内容栏id为 content 。可以通过下面方法得到 content:ViewGroup content = findViewById(R.android.id.content) 。通过 content.getChildAt(0) 可以得到设置的 view 。 DecorView 其实是一个 FrameLayout , View 层的事件都先经过 DecorView ,然后才传递给我们的 View 。

        MeasureSpec 代表一个位的int值,高2位代表 SpecMode ,低位代表 SpecSize , SpecMode 是指测量模式,而 SpecSize 是指在某种测量模式下的规格大小。

        SpecMode 有三类,如下所示:

        UNSPECIFIED

        EXACTLY

        AT_MOST

        LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽/高。

        对于顶级View,即DecorView和普通View来说,MeasureSpec的转换过程略有不同。对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams共同确定;

        对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams共同决定;

        MeasureSpec一旦确定,onMeasure就可以确定View的测量宽/高。

        小结一下

        当子 View 的宽高采用 wrap_content 时,不管父容器的模式是精确模式还是最大模式,子 View 的模式总是最大模式+父容器的剩余空间。

        View 的工作流程主要是指 measure 、 layout 、 draw 三大流程,即测量、布局、绘制。其中 measure 确定 View 的测量宽/高, layout 确定 view 的最终宽/高和四个顶点的位置,而 draw 则将 View 绘制在屏幕上。

        measure 过程要分情况,如果只是一个原始的 view ,则通过 measure 方法就完成了其测量过程,如果是一个 ViewGroup ,除了完成自己的测量过程外,还会遍历调用所有子元素的 measure 方法,各个子元素再递归去执行这个流程。

        如果是一个原始的 View,那么通过 measure 方法就完成了测量过程,在 measure 方法中会去调用 View 的 onMeasure 方法,View 类里面定义了 onMeasure 方法的默认实现:

        先看一下 getSuggestedMinimumWidth 和 getSuggestedMinimumHeight 方法的源码:

        可以看到, getMinimumWidth 方法获取的是 Drawable 的原始宽度。如果存在原始宽度(即满足 intrinsicWidth > 0),那么直接返回原始宽度即可;如果不存在原始宽度(即不满足 intrinsicWidth > 0),那么就返回 0。

        接着看最重要的 getDefaultSize 方法:

        如果 specMode 为 MeasureSpec.UNSPECIFIED 即未指定模式,那么返回由方法参数传递过来的尺寸作为 View 的测量宽度和高度;

        如果 specMode 不是 MeasureSpec.UNSPECIFIED 即是最大模式或者精确模式,那么返回从 measureSpec 中取出的 specSize 作为 View 测量后的宽度和高度。

        看一下刚才的表格:

        当 specMode 为 EXACTLY 或者 AT_MOST 时,View 的布局参数为 wrap_content 或者 match_parent 时,给 View 的 specSize 都是 parentSize 。这会比建议的最小宽高要大。这是不符合我们的预期的。因为我们给 View 设置 wrap_content 是希望View的大小刚好可以包裹它的内容。

        因此:

        如果是一个 ViewGroup,除了完成自己的 measure 过程以外,还会遍历去调用所有子元素的 measure 方法,各个子元素再递归去执行 measure 过程。

        ViewGroup 并没有重写 View 的 onMeasure 方法,但是它提供了 measureChildren、measureChild、measureChildWithMargins 这几个方法专门用于测量子元素。

        如果是 View 的话,那么在它的 layout 方法中就确定了自身的位置(具体来说是通过 setFrame 方法来设定 View 的四个顶点的位置,即初始化 mLeft , mRight , mTop , mBottom 这四个值), layout 过程就结束了。

        如果是 ViewGroup 的话,那么在它的 layout 方法中只是确定了 ViewGroup 自身的位置,要确定子元素的位置,就需要重写 onLayout 方法;在 onLayout 方法中,会调用子元素的 layout 方法,子元素在它的 layout 方法中确定自己的位置,这样一层一层地传递下去完成整个 View 树的 layout 过程。

        layout 方法的作用是确定 View 本身的位置,即设定 View 的四个顶点的位置,这样就确定了 View 在父容器中的位置;

        onLayout 方法的作用是父容器确定子元素的位置,这个方法在 View 中是空实现,因为 View 没有子元素了,在 ViewGroup 中则进行抽象化,它的子类必须实现这个方法。

        1.绘制背景( background.draw(canvas); );

        2.绘制自己( onDraw );

        3.绘制 children( dispatchDraw(canvas) );

        4.绘制装饰( onDrawScrollBars )。

        dispatchDraw 方法的调用是在 onDraw 方法之后,也就是说,总是先绘制自己再绘制子 View 。

        对于 View 类来说, dispatchDraw 方法是空实现的,对于 ViewGroup 类来说, dispatchDraw 方法是有具体实现的。

        通过 dispatchDraw 来传递的。 dispatchDraw 会遍历调用子元素的 draw 方法,如此 draw 事件就一层一层传递了下去。dispatchDraw 在 View 类中是空实现的,在 ViewGroup 类中是真正实现的。

        如果一个 View 不需要绘制任何内容,那么就设置这个标记为 true,系统会进行进一步的优化。

        当创建的自定义控件继承于 ViewGroup 并且不具备绘制功能时,就可以开启这个标记,便于系统进行后续的优化;当明确知道一个 ViewGroup 需要通过 onDraw 绘制内容时,需要关闭这个标记。

        参考:《Android开发艺术探索》

android的自定义View的实现原理?哪位能给我个思路呢。谢谢。

       如果说要按类型来划分的话,自定义View的实现方式大概可以分为三种,自绘控件、组合控件、以及继承控件。那么下面我们就来依次学习一下,每种方式分别是如何自定义View的。

       一、自绘控件

       自绘控件的意思就是,这个View上所展现的内容全部都是我们自己绘制出来的。绘制的代码是写在onDraw()方法中的,而这部分内容我们已经在 Android视图绘制流程完全解析,带你一步步深入了解View(二) 中学习过了。

       下面我们准备来自定义一个计数器View,出售软件商城源码这个View可以响应用户的点击事件,并自动记录一共点击了多少次。新建一个CounterView继承自View,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>  

       <RelativeLayout xmlns:android="/apk/res/android"  

           android:layout_width="match_parent"  

           android:layout_height="dp"  

           android:background="#ffcb" >  

         

           <Button  

               android:id="@+id/button_left"  

               android:layout_width="dp"  

               android:layout_height="dp"  

               android:layout_centerVertical="true"  

               android:layout_marginLeft="5dp"  

               android:background="@drawable/back_button"  

               android:text="Back"  

               android:textColor="#fff" />  

         

           <TextView  

               android:id="@+id/title_text"  

               android:layout_width="wrap_content"  

               android:layout_height="wrap_content"  

               android:layout_centerInParent="true"  

               android:text="This is Title"  

               android:textColor="#fff"  

               android:textSize="sp" />  

         

       </RelativeLayout>

       在这个布局文件中,我们首先定义了一个RelativeLayout作为背景布局,然后在这个布局里定义了一个Button和一个TextView,Button就是标题栏中的返回按钮,TextView就是标题栏中的显示的文字。

       接下来创建一个TitleView继承自FrameLayout,代码如下所示:

public class TitleView extends FrameLayout {   

         

           private Button leftButton;  

         

           private TextView titleText;  

         

           public TitleView(Context context, AttributeSet attrs) {   

               super(context, attrs);  

               LayoutInflater.from(context).inflate(R.layout.title, this);  

               titleText = (TextView) findViewById(R.id.title_text);  

               leftButton = (Button) findViewById(R.id.button_left);  

               leftButton.setOnClickListener(new OnClickListener() {   

                   @Override  

                   public void onClick(View v) {   

                       ((Activity) getContext()).finish();  

                   }  

               });  

           }  

         

           public void setTitleText(String text) {   

               titleText.setText(text);  

           }  

         

           public void setLeftButtonText(String text) {   

               leftButton.setText(text);  

           }  

         

           public void setLeftButtonListener(OnClickListener l) {   

               leftButton.setOnClickListener(l);  

           }  

         

       }

       TitleView中的代码非常简单,在TitleView的构建方法中,我们调用了LayoutInflater的inflate()方法来加载刚刚定义的title.xml布局,这部分内容我们已经在 Android LayoutInflater原理分析,带你一步步深入了解View(一) 这篇文章中学习过了。

       接下来调用findViewById()方法获取到了返回按钮的实例,然后在它的onClick事件中调用finish()方法来关闭当前的Activity,也就相当于实现返回功能了。

       另外,为了让TitleView有更强地扩展性,我们还提供了setTitleText()、setLeftButtonText()、setLeftButtonListener()等方法,分别用于设置标题栏上的文字、返回按钮上的文字、以及返回按钮的点击事件。

       到了这里,一个自定义的标题栏就完成了,那么下面又到了如何引用这个自定义View的部分,其实方法基本都是相同的,在布局文件中添加如下代码:

<RelativeLayout xmlns:android="/apk/res/android"  

           xmlns:tools="/tools"  

           android:layout_width="match_parent"  

           android:layout_height="match_parent" >  

         

           <com.example.customview.TitleView  

               android:id="@+id/title_view"  

               android:layout_width="match_parent"  

               android:layout_height="wrap_content" >  

           </com.example.customview.TitleView>  

         

       </RelativeLayout> 

       这样就成功将一个标题栏控件引入到布局文件中了,运行一下程序。

       现在点击一下Back按钮,就可以关闭当前的Activity了。如果你想要修改标题栏上显示的内容,或者返回按钮的默认事件,只需要在Activity中通过findViewById()方法得到TitleView的实例,然后调用setTitleText()、setLeftButtonText()、setLeftButtonListener()等方法进行设置就OK了。

android自定义view

       android怎么自定义view呢?不知道的小伙伴来看看小编今天的分享吧!

       android可以通过组合控件来实现自定义view。组合控件就是将系统原有的控件进行组合,构成一个新的控件。这种方式下,不需要开发者自己去绘制图上显示的内容,也不需要开发者重写onMeasure,onLayout,onDraw方法来实现测量、布局以及draw流程。

       å…·ä½“操作:

       1、定义标题栏布局文件

       å®šä¹‰æ ‡é¢˜æ çš„布局文件custom_title_view.xml,将返回按钮和标题文本进行组合。这一步用于确定标题栏的样子,代码如下所示:

xmlversion="1.0"encoding="utf-8"?

       RelativeLayoutxmlns:android="/apk/res/android"

       android:layout_width="match_parent"

       android:layout_height="wrap_content"

       android:background="@android:color/holo_orange_light"

       Button

       android:id="@+id/btn_left"

       android:layout_width="wrap_content"

       android:layout_height="wrap_content"

       android:layout_centerVertical="true"

       android:layout_marginLeft="5dp"

       android:text="Back"

       android:textColor="@android:color/white"/

       TextView

       android:id="@+id/title_tv"

       android:layout_width="wrap_content"

       android:layout_height="wrap_content"

       android:layout_centerInParent="true"

       android:text="Title"

       android:textColor="@android:color/white"

       android:textSize="sp"/

       /RelativeLayout

       2、根据给定布局实现自定义View

       publicclassCustomTitleViewextendsFrameLayoutimplementsView.OnClickListener{

       privateView.OnClickListenermLeftOnClickListener;

       privateButtonmBackBtn;

       privateTextViewmTittleView;

       publicCustomTitleView(@NonNullContextcontext,@NullableAttributeSetattrs){

       super(context,attrs);

       LayoutInflater.from(context).inflate(R.layout.custom_title_view,this);

       mBackBtn=findViewById(R.id.btn_left);

       mBackBtn.setOnClickListener(this);

       mTittleView=findViewById(R.id.title_tv);

       }

       @Override

       publicvoidonClick(Viewv){

       switch(v.getId()){

       caseR.id.btn_left:

       if(mLeftOnClickListener!=null){

       mLeftOnClickListener.onClick(v);

       }

       break;

       }

       }

       publicvoidsetLeftOnClickListener(View.OnClickListenerleftOnClickListener){

       mLeftOnClickListener=leftOnClickListener;

       }

       publicvoidsetTittle(Stringtitle){

       mTittleView.setText(title);

       }

       }

       è¯´æ˜Žï¼š

       ï¼ˆ1)代码中对外提供了两个接口,一是动态设置标题,二是使用者可以自定义返回按钮的点击事件。

       ï¼ˆ2)CustomTitleView的构造函数,要选择两个参数的,选择其它参数的构造函数会报错。这一点是笔者开发机测试的结果,暂时不清楚是不是所有手机上都是这样。

       ï¼ˆ3)这里是继承的FrameLayout,但是继承LinearLayout,RelativeLayout等系统布局控件都可以。之所以要继承这些系统现成的ViewGroup,是因为这样可以不用再重写onMeasure,onLayout等,这样省事很多。由于这里是一个布局控件,要用LayoutInflater来填充,所以需要继承ViewGroup,如果继承View的直接子类,编译会不通过。所以,CustomTitleView自己就是一个容器,完全可以当成容器使用,此时CustomTitleView自身的内容会和其作为父布局添加的子控件,效果会叠加,具体的叠加效果是根据继承的容器特性决定的。

       3、在Activity的布局文件中添加CustomTitleView。

       åœ¨Activity的布局文件activity_custom_view_compose_demo.xml中,像使用系统控件一样使用CustomTitleView即可。CustomTitleView自己就是继承的现成的系统布局,所以它们拥有的属性特性,CustomTitleView一样拥有。

xmlversion="1.0"encoding="utf-8"?

       RelativeLayoutxmlns:android="/apk/res/android"

       android:layout_width="match_parent"

       android:layout_height="match_parent"

       com.example.demos.customviewdemo.CustomTitleView

       android:id="@+id/customview_title"

       android:layout_width="match_parent"

       android:layout_height="wrap_content"

       /com.example.demos.customviewdemo.CustomTitleView

       /RelativeLayout

       4、在Activity中操作CustomTitleView,代码如下:

       1publicclassCustomViewComposeDemoActivityextendsAppCompatActivity{ privateCustomTitleViewmCustomTitleView;4@Override5protectedvoidonCreate(BundlesavedInstanceState){ 6super.onCreate(savedInstanceState);7setContentView(R.layout.activity_custom_view_compose_demo);8mCustomTitleView=findViewById(R.id.customview_title);9mCustomTitleView.setTittle("ThisisTitle");mCustomTitleView.setLeftOnClickListener(newView.OnClickListener(){ @OverridepublicvoidonClick(Viewv){ finish();}});}}

       åœ¨ç¬¬8行中,获取到CustomTitleView实例,第9行设置标题文字,第行自定义“Back”按钮点击事件。

       5、效果图

       æŒ‰ç…§å¦‚上的4步,就通过组合控件完成了一个比较简单的自定义标题栏。

       ä»¥ä¸Šå°±æ˜¯å°ç¼–今天的分享了,希望可以帮助到大家。

Android自定义view实现圆环效果详解

       如何实现自定义的view效果是关键

       (1)首先,需要创建一个类,继承自View,并重写onDraw和onMeasure方法

       (2)在实现过程中,会用到一些特定的类

       (3)需要编写attrs.xml文件

       (4)接着,在activity_main布局文件中进行相应的布局编写

       (6)编程实现圆环效果

       总结

       本文详细介绍了Android自定义view实现圆环效果的实例代码,旨在为大家提供帮助。如有任何疑问,欢迎在评论区留言,我会及时回复。同时,也感谢大家的支持!

最简最全,一文搞定Android WebView编译+AOSP集成

       对于Android开发者来说,Android WebView是不可或缺的内置组件,它提供了一键可用的网页浏览功能。然而,WebView作为系统组件,其版本更新受限于系统级别的开发,可能导致HTML5、ES、CSS特性支持不足。本文将详细介绍如何从Chromium源码编译定制WebView,以及如何集成到AOSP系统中。

       首先,确保你已经下载并配置好Chromium源码。编译时,使用gn命令生成args.gn文件,其中需新增system_webview_package_name选项来设置自定义APK包名,特别注意不同Android版本的WebView包名差异。编译目标有三种:system_webview_apk(适用于5.0及以上,独立APK)、monochrome_public_apk(包含WebView和Chrome,适用于自开发系统)和trichrome_webview_apk(适用于Android +,采用aab拆分)。

       编译完成后,根据目标选择对应的APK,如system_webview_apk将生成一个SystemWebview.apk,包内包含WebView DevTools,用于调试。通过修改args.gn文件中的包名,确保与系统预装WebView的版本一致。如果在非AOSP系统中,可能需要使用adb或其他工具检查并修改包名。

       在编译过程中,还需注意在系统中卸载预装的WebView以避免签名冲突。使用adb脚本进行一键卸载,然后将编译好的APK安装到设备,可能还需修改WebView提供者以指向新安装的版本。

       对于AOSP集成,虽然预编译的WebView在AOSP中可用,但建议使用自编译的最新稳定版。根据目标Android版本选择合适的Chromium稳定版代码,并注意兼容性问题。编译正式发布版本时,需设置is_official_build和proprietary_codecs等选项,同时考虑视频编解码的许可证问题。

       最后,对于私有签名、包名修改、系统镜像集成以及Android框架的修改,都有详细的步骤和注意事项。编译WebView并成功集成到AOSP后,可以确保为用户提供最新、定制化的浏览器体验。

Android中View的创建过程

        我们知道在onCreate里面View还是没有测绘完成的。那么什么时候测绘完成了?答案是onResume。

        通过查看源码 我们可以看到在onCreate方法里面调用了getWindow()方法然后在将我们的页面塞到这个window里面。这个window也就是PhonwWindow.

        那PhoneWindow是什么时候被创建的?

        这就引出了Activity的创建流程。

那Activity是怎么被创建的呢?

        由于Activity是一个组件他是由系统使用ActivityThread方法去创建的。

        现在我来分析下:

        先来到ActivityThread类的handleLaunchActivity方法。

        可以看到他去调用了Activity的performCreate方法。

        现在我们终于看到onCreate方法被调用了。

        这里还有个重点,在performLaunchActivity里面去调用Activity的onCreate方法之前还去做了一件很重要的事情,这个事情在第行:调用了Activity的attach方法。

        现在跟到Activity的attach方法:找到了我们一直找的PhoneWindow的创建。

关键词:个人Vlog网站源码

copyright © 2016 powered by 皮皮网   sitemap