春節從冬至開始_網頁設計公司

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

晉城麵塑 資料圖片

臘月里蒸麥芽饃 資料圖片

臘月里蒸麥芽饃 資料圖片

過年是從冬至開始的。從冬至到臘八,從臘八到小年,從小年到大年,人們沿襲着祖先傳下來的風俗,操持着,準備着,迎接着,春節的氣氛也在幸福的忙碌中漸漸達到頂點。年俗是文化,也是人情,生生不息,代代相傳。

夫冬至之節,陽氣始萌

“天時人事日相催,冬至陽生春又來。”這是杜甫的七律《小至》的兩句詩。杜工部是對的,春天是隨着冬至來到人間的。

如果認為春節代表春天的到來,那就錯了。其實我們所過的春節僅僅只是春天的一個節日。春節到來的時候,人間早已經屬於春天了。春節只是給了我們一個春天已經來到人間的強烈感覺。或者說,春節只是慶祝春天已經來到人間的一個隆重儀式。

而真正意義上的春天,是從冬至開始的。我們在這裏所說“真正意義”,是指中國古代哲學中的實在意義。我們的先哲們認為,宇宙的萬事萬物無不在陰陽交替、互動、對立、統一中生存和消長。整個世界,是由陰和陽組成的。陰與陽,渾藏於天地間,是中國古代哲學的文化內核。陰與陽也因此易位於冬至和夏至兩個節日之間。夏至之後,日漸短,夜漸長,陰漸盛,陽漸衰,所有事物,所有生命,都在漸趨低谷,即“萬物收斂”。冬至之後,晝漸長,夜漸短,“一天長一線,十天長一截”。然而,冬至過後,天氣會一天比一天冷。從冬至節那一天起,就開始了數九天。“一九二九,關門袖手”“三九四九,凍破碓臼”,天氣的確是越來越冷了。

“夫冬至之節,陽氣始萌。”雖然正值數九寒天,春潮卻在暗中涌動。陽氣在暗中上升,物候的腳步在不知不覺中邁向了春天。春如初胎,從冬至那天起,已經沉靜地安附於大自然的宮室里,正如《西遊記》中邵康節所說:“冬至子之半,天心無改移。一陽初動處,萬物未生時。”“五九六九,沿河看柳。”“七九河開,八九雁來。”“九九加一九,耕牛遍地走。”說明陽氣在暗中悄悄上升,春天在不聲不響走近人間。這一點也被羅貫中寫進了《三國演義》。程昱入告曹操:“今日東南風起,宜預提防。”操笑曰:“冬至一陽生,來複之時,安得無東南風?”把一場赤壁之戰放在冬至,顯得波瀾壯闊,意味無窮。

中國“四大名著”無不拿冬至說事。曹雪芹在《紅樓夢》更是把冬至說到特別:“這年正是十一月三十日冬至。到交節的那幾日,賈母、王夫人、鳳姐兒日日差人去看秦氏,回來的人都說:‘這幾日也沒見添病,也不見甚好。’王夫人向賈母說:‘這個癥候,遇着這樣大節不添病,就有好大的指望了。’”說明冬至是個不同尋常節令,不光宇宙間萬物在蘇醒、發生、成長,就連病疽疾癘也一樣不會放過其生命活躍起來的機會。

《水滸傳》似乎沒有必要寫到冬至,但施耐庵並沒有放過這一個重要節點:“聞知今上大張燈火,與民同樂,慶賞元宵。自冬至后,便造起燈,至今才完。”我們先不說作者在敘事中來上這麼一筆的用意是什麼,施耐庵給了我們一個信息:春節乃至元宵節都是從冬至開始的。

其實春節不只是從冬至開始的,春節在中國古代與冬至是並肩同行的。

《詩經·七月》:“七月流火,九月授衣。一之日觱發,二之日栗烈。無衣無褐,何以卒歲?三之日於耜,四之日舉趾。”“一之日”是周曆和豳歷的正月。而周曆和豳歷的正月正是夏曆的十一月,就是我們現在所過的冬至的月份。而今的冬至在十一月末,或十二月初,春節則在正月初一,之間相隔時間近一個月,這並不是春節與冬至分道揚鑣,而是我們選擇了夏曆,即農曆。所以,春節是從冬至開始的,有它的歷史淵源,也有它的文化根據。

摔老南瓜

今天,儘管春節姍姍來遲於冬至之後,但人們並沒有忘記春節是從冬至開始的。尤其是在我的家鄉晉東南,依然回蕩着歷史的流響餘韻,也總是忘不了想方設法給冬至塗上一點文化色彩,讓冬至節沉染在豐富的文化意蘊中。

冬至節的五更天,天還不明,就能聽到各家各戶“嗵嗵”的響聲傳出來。那是家家都在摔老南瓜。秋天收穫老南瓜的時候,家家都要挑好的大的收藏起來,等到冬至的五更天,把老南瓜從架上取下來,抱到炕頭上,高高舉起,重重地摔到地上,南瓜子濺得到處都是。驚醒的孩子們會問父母,冬至為什麼摔老南瓜?父母會告訴孩子們冬至摔老南瓜的故事:

傳說有人要在冬至那天夜裡刺殺孔子,藏匿孔子那一家人,暗中放走了孔子,在孔子睡覺的枕頭上放了個老南瓜,用被子蓋上,偽裝成孔子正在睡覺。刺客夜入寢室,舉刀便砍,只聽“咔嚓”一聲,半個老南瓜“嗵”的一聲滾落到地上,刺客以為大功造成。於是,冬至摔老南瓜就成了祀孔的一種特殊形式。

冬至節,村子里和小學校也要祀孔。冬至那天學校放假一天,說是放假,學生並不離開學校,只是不準翻書而已。意思是,翻書會迷了孔夫子的眼。學生要穿上新衣,跟着村幹部和老師攜酒脯祀孔。祀孔結束后,村幹部陪老師吃油疙麻雜燴菜,學生回家吃餃子。我們把餃子叫“凍耳朵”,意思是,吃了凍耳朵,耳朵不怕凍壞。

冬至摔成碎塊的老南瓜,可以煮成“紅小豆老南瓜”吃。色質特別好的,留給“五豆”和“臘八”。臘月初五是“五豆節”,小節。家家吃紅豆小米撈飯,即用紅豆和小米一起燜的乾飯。五豆是冬至到臘八的一個小小跳板。

臘八粥

臘月初八,家家吃“臘八粥”。

“粥”是外鄉人的叫法,我們不叫粥,實質上也不是粥。我們叫黏米飯,也叫黏飯,又叫甜飯,實實在在的“飯”,能夠用筷子夾起來的。黏米與小米顏色一樣,黃燦燦的,顆粒大小也相同,外地人用眼睛是分辨不出來的。黏米做成的“黏米飯”特別“黏”,特別香。

臘月初七晚上,母親就要煮紅豆。紅紅的蘭花炭火,煮豆的鍋在火上嘩啦嘩啦響,孩子們的夢裡也是黏米飯香。

半夜時分,豆子煮熟了,母親會將老南瓜、黏米、紅薯、紅棗、核桃、柿餅、柿圪蓮,花生、紅糖、白糠等依次入鍋。什麼東西什麼時間放,既不能早,也不能遲。比如花生,早了不脆,遲了不熟;比如柿餅柿圪蓮,時間不對,不但不甜,還會發澀。做黏米飯最好用砂鍋,攪黏米飯最好用木板。用鐵傢伙做出來的黏米飯顏色不鮮明,吃起來味道也不對。對着黏米飯鍋不能說話,不能咳嗽,如果把唾沫星兒噴到黏米飯里,黏米飯就會“涎”成米是米、水是水,不融和,也不黏稠。

早晨,軟米飯做好了,各家各戶要相互饋贈。送贈黏米飯的任務大都是孩子們的事。一碗碗紅紅的黏米飯,像一盞盞的小紅燈籠,像一捧捧的火焰,像一朵朵鮮艷的花,在飛雪中,在白雪皚皚的大街小巷裡,燃燒,跳躍,綻放,如竄梭一般地來來去去。每碗黏米飯都是滿滿的,偶爾會滴一點在雪地上,像掉在雪地上的一塊紅紅的火炭,像一枝火的花朵,讓雪天顯得溫暖,讓雪天顯得爛漫,點燃着鄉村裡永遠的鄉愁。

家家戶戶都做黏米飯,家家戶戶相互饋贈,那是一種風俗。你送我一碗,我送你一碗,“親戚箢還箢,鄰家碗還碗”。那是鄉里鄉親熱絡感情、鞏固關係的一種最簡單、最樸素的一種方式。千年不變,也不應該變。

學生都要給老師送軟米飯,老師收很多黏米飯,凍起來,天天拿鏊子焐着吃,放一點油,焐出一層薄薄的皮,又香又甜,天下風味。

也有送黏米飯給村子里的藥鋪商鋪的,藥鋪商鋪也會準備一些核桃、紅棗、花生、柿餅之類回贈。

為什麼要吃臘八粥?為什麼要吃黏米飯?歷來傳說不一。但最好緣由應該是紀念佛祖成道日,效法牧女獻乳糜於佛的故事。不過,這也是人們賦予臘八節的一種文化色調。我看,其蘊包含在《詩經》里。

《詩經》有言:“物其多矣,維其嘉矣。物其旨矣,維其偕矣。物其有矣,維其時矣。”冬至時節,乃至整個臘月天,各種糧食果蔬藏儲豐富,是乃“物其多矣”“物其旨矣”“物其有矣”;選擇節日,如冬至、五豆、臘八、小年,做成合節的諸如黏米飯、豆撈飯、火燒之類的飯食,乃是“維其嘉矣”“維其偕矣”“維其時矣”。也就說,臘八吃黏米飯才有臘八氣氛,才是臘八的味道。也如八月十五吃月餅,三月三吃春餅,端午節吃粽子,此即“物其有矣,維其時矣”。

冬至說儒,臘八說佛,小年以至大年,就應該說“道”了。佛、儒、道,三家文化,融化了我家鄉的臘月天。

過了臘八,接着就是小年。臘月二十三,祭灶的日子到了。

祭灶祭哪個?祭祝融。《周禮》說:“顓頊氏有子日黎,為祝融,祀以為灶神。”漢之後,灶王爺掌握了人們的禍福、壽夭,出現了“灶神晦日歸天,白人罪”的說法。人們小心奉祀灶君,傳說灶君每年臘月二十三都要到天帝那兒去陳說人間的善與惡,因此各家各戶要為灶王爺送行,打些火燒給灶爺做乾糧,弄些麩皮草料給灶爺秣灶馬,在火口上塗些糖餳給老灶爺糊嘴巴,好讓老灶爺到天帝那兒多說好話,把天帝所降的吉祥帶回人間。

“二十三祭罷灶,夾上包袱往回趵。”住在娘家的媳婦,祭過灶之後,就該回到婆婆家準備過年了。

“好了!好了!到跟前兒都好了!”

過了冬至節,男人們忙着殺豬,宰羊,割肉,買菜,辦年貨。女人碾米,磨面,扯布,買衣線,做新衣,做新鞋。過了臘月二十三,就要掃屋子,做豆腐,坐蒸鍋,蹲油鍋。

晉城不缺香炭,火邊圍一圈香炭,把火燒得旺旺的,女人們各顯身手,蒸棗山、棗供、棗花、豬、羊、小兔兒、小魚兒、小刺蝟,炸菱花、饊花。所蒸所煮,都是敬神的供品。棗山像個大青蛙,背上許多面花兒。每朵花上都有一個紅棗,看起來像一朵盛開的大紅花。雜獻只是一個大饅頭,上邊摞幾層面花兒,最上面安一個大紅棗。比雜獻小一點的叫棗花兒,只有一層花,花上邊只是一個酸棗。棗山和雜獻上的花兒,有盤花,有剪花。盤花像牡丹,剪花像菊花。把筷子頭破開,析出一朵“小梅花”。在小小的瓷碟子里化一點品紅,把“小梅花”在品紅里蘸蘸,在棗山和雜獻上點上朵朵小梅花。將高粱秸破成兩半,摳去瓤子,便成為一個彎彎的月牙。把月牙兒蘸一點品綠,在“小梅花”的四角點四片,好花有了綠恭弘=叶 恭弘扶持,所有的獻供便飛一片春色。每朵花都有兩層三層,看上去重重疊疊,有點“層巒聳翠”的意思。

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

給女兒女婿蒸兩個大口禮饃,個子比頭盔大。把揉好的面用細繩從頂向下勒一個十字,蒸熟的口禮饃如四瓣盛開的花朵,點綴上品紅品綠,顯得喜氣洋洋,一片祥和。

給自己家裡每個人蒸一個“人口禮饃”,其中一個裡邊放個“制錢”,其餘放上一個紅棗。初七人日,誰拿到放“制錢”的口禮饃,誰運氣就是最好的,福氣也是最大的,大事小事都會成為家裡的靠山。

煮一鍋玉茭豆兒,蒸些豆餡饃、菜餡饃。從初二到初五,每天早晨喝玉茭豆兒,吃餡饃。

為了一個年節,家家戶戶都會忙得團團轉,腳步總是匆匆的。人與人見了面總要關切地問一聲:“準備好了嗎?”總是笑着回答:“好了!好了!到跟前兒都好了!”

洗了澡,理了發。糊了窗戶,架起正火。貼上對聯,掃了院子,就到大年三十了。

“二十三日去,初一五更回”

三十的晚上,家家戶戶吃“面恭弘=叶 恭弘”。脫玉茭、大白菜、紅蘿蔔、豆腐、豆芽,一鍋煮熟。寬寬的麵條,不放蔥蒜,只放花椒,因為要獻祖先。先盛一碗獻祖宗。燃上一炷香,大門口放三個炮,接祖宗回家過年,祖先就會跟着香煙走進大門,走進屋門。祖先畢竟是泉鄉之物,沒有香煙引路,門神不會放祖先進入家門。

把祖先請到祖先桌上,給祖先獻上一碗面恭弘=叶 恭弘,俗稱“爺奶奶湯兒”,讓祖宗先喝口湯,歇歇氣,等五更天敬神時,再給祖宗上獻。

從臘月二十三到年三十,人間是沒有神的,諸神都到天帝那裡參加年會了。這一段時間,人間是無忌諱的,人們可以為所欲為,特別是結婚辦喜事,不用“擇好兒”,想哪天辦就哪天辦。

臘月三十,祖宗也回來了,家人與祖宗一起,早早就要睡覺了。睡覺時放一個“關門炮”,說明這一年已經過完了,關門了,再開門的時候就是春節了。

神回到人間的時間,是正月初一的五更天,是所謂的“二十三日去,初一五更回”。初一五更,起床先放一個開門炮,春節的大門從此就打開了。

不論農家、官家與商家,不論窮家與富家,家家供奉灶爺,供奉天地爺、家堂老爺、財神爺,其他便不相同了。有的家庭供奉不知名姓的神仙,總稱仙家老爺。很多人家都供奉佛爺,讀書人供奉孔夫子,木匠供奉魯班,鐵匠供奉老君。

其實行走在我們桑梓間的不止佛、儒、道,還有天地、山神、龍王、馬王、牛王、青龍、白虎、紫姑、門神、樹神、花神、五道神、土地神,等等。當然,林林總總的神仙,也都出身於道家,平時不聞不問,過年時,不知道從哪裡蹣跚走來,在一元始旦,萬象更新,人世間充滿歡樂充滿希望的時候,同眾庶一起歡度春節,增加節日的氣氛,也讓人們的心靈有所依傍,靈魂有所棲止,精神有所寄託。

初一五更,點正火,接神,焚香。天地爺自是“滿斗焚香”,一炷炷的單香插得如滿天星斗。其他佛家道家的神仙都是三炷香,或者一炷整香。祖先不是神,所以不可與神一起受享獻饗。各種獻供只能等敬罷神,酌回來才能獻給祖先享用。

正火點着之後,青煙裊裊,火光衝天,火星直冒,紅光滿園,柏樹枝的香氣格外讓人心清氣爽。孩子們會圍着正火又跑又跳,又喊又笑,小臉一張張映得通紅,朝氣與喜氣沖溢在整個院子里。焚香的同時放鞭炮,孩子們手裡會提個小小的紅燈籠,到處搶鞭炮。大人們會告訴孩子,大年初一去抱住大椿樹,高聲喊:“椿樹娘,椿樹娘,你長高了做大梁,我長高了領衣裳。”希望椿樹帶領孩子與孩子一起長高。

初一五更,男人敬神,女人打火做飯。大年初一早晨多有吃拉麵的,意思全家互相拉着點,拉緊點,不要丟了一個,不要少了一個。女人拉麵,男人批蒜苗。白生生的面,拉得又長又細。頭一碗先送本院鄰居,鄰居也會送一碗過來,是鄰裡間年節時的一種交往。

吃過飯,父母要給孩派壓歲錢,還要教孩子怎麼拜年。同輩人見了面,也要互相說一聲:“拜年了!”“春節好!”“恭喜發財!”村子上的老會頭要飾鑼集中八音會,耍會,唱戲。

初五晚上要送祖先。端上半碗爺奶奶湯,燃上一炷香,隨着香煙裊裊,把祖宗送到大門口,放三個炮,年節基本就結束了。

元宵拱燈棚

說是年基本結束了,就是還沒有完全結束。還有個正月十五鬧元宵。

掛燈是我們家鄉一個古老的傳統,不僅家家門口掛燈,整個村子里也掛燈。我們村裡有好幾百對花燈,全部掛在後街上。後街從東到西二里長,燈棚用紅、黃、藍三色小布編成小方花格子作燈棚,把燈並排掛在燈棚下,橫看是一對一對的,順着街道看過去,像兩條火龍,從東頭一直蜿蜒到西頭。

吃晚飯時,鑼聲一響,人們都知道,要髮蠟了。二里長街,分段管理,負責髮蠟。髮蠟,就是點燃蠟燭的意思。蠟燭是用羊油掂的。熬上一大鍋羊油,把棉花做成的燭芯纏在蠟柱上,往大鍋里一沾一掂,反覆多次,便掂成一支蠟燭。白蠟是本色,紅蠟是添加了顏料。紅蠟白蠟都搖紅,把一條長街搖得輝煌壯麗。

蠟是年年要掂的,燈也是年年要掛的。即使最困難時期,也沒有停了掛燈。人們說:“討吃看煙火,肚飢眼歡樂。”可以不吃飯,卻不能不掛燈,好不容易又一年了。也許是想證明人這一年又活過來了。

燈棚下每隔十來步就有一爐正火。燒正火用的是香煤香炭,也叫蘭花香炭,也叫白煤,傳說是英國女王燒壁爐必用之物。燒正火不能用臭炭,否則會嗆得人打噴嚏,既出洋相,又不吉祥。

傍晚時分,把大塊的蘭花香炭添足,正火很快就燒起來,一堆火焰熊熊,一堆金光燦爛。天上月光,棚下燈光,街邊火光,加上社鼓笙簫,一派金碧輝煌。

後街的燈全是紗燈,繪有《三國演義》《封神榜》《水滸傳》人物情節。觀燈的人從燈棚下拱來拱去,拱燈棚大都是年輕人。拄着棍子的老人,引着孫兒老叟,傴僂着腰的老嫗,把孩子架在膀子上的父親,梳了圓頭的母親,大都圍在燈棚下,每盞燈下都會圍一個圈,仄着耳朵,聽老人們說些三國水滸的故事。

年輕人很少站在燈棚下看那些老故事。他們有自己的故事。“月上柳梢頭,人約黃昏后”,是他們故事的藍本。他們總是以“拱燈棚”,演繹他們的故事。

“拱燈棚”是小鎮一個古老的風俗。按風俗,不管男女老少,正月十五拱燈棚會吉祥如意,會免除許許多多的大災小難。

不管是男孩子還是女孩子,他們總是三個一群兩個一夥,貌似看燈,卻心不在焉。

女孩子們總手拉着手“拱燈棚”。從東頭拱到西頭,再從西頭拱到東頭。人前,不管內心多麼豐富,表面都是很文靜的,悄悄謐謐的,總是很害羞的樣子。如果不得不笑,也只是趕緊把嘴捂住,彎下腰,或者背過身,悄抿了嘴,齒頰之間發出一點吹涼風一樣的“噝噝”聲。當然,有時候也難免會笑出聲來,無非是忽然想起了,或者忽然看到了可笑的人和事,實在是忍不住了,就笑出來。一個人“疙啼”一聲,會傳染女孩子們無端“疙啼疙啼”笑成一堆。

有話說的時候,也只是悄聲細語,低聲喁喁。心裏卻熱烈。本來是三個兩個相跟着,牽着手,卻不知道什麼時候就少了一個。不知道其中哪個女孩什麼時候就拱出了燈棚,拱到了月光照不到,燈光也照不到的地方去了。

當然,過來人都知道,那燈火闌珊的角落有女孩子和男孩子的故事。他們知道,入春了,驚蟄了,野草也該句萌了。

藿谷洞的燈與後街不同,藿谷洞的燈不是為大眾看的,是專奉“四奶奶”即送子娘娘的,觀燈的人大都是母親或祖母,因此藿谷洞的燈上沒有故事,燈下也沒有故事,很雅靜,很純凈,很雅緻,雅緻到脫俗。燈的形式也很單純,一式宮燈,兩種燈罩。布罩和玻璃。布罩是綠裙紅褲,很鮮艷;玻璃罩繪有梅、蘭、竹、菊,石榴,荷花,魚兒,鳥兒,題些“玉燭豐年”“雨暘時若”“天地交泰”的文字。品位很高,有一點閨閣氣。倘若天空中有雪花飄下來,那才是真正的天上人間。後街的燈如果代表“俗”,那麼藿谷洞的燈就是一個“雅”。從後街到藿谷洞,就是一個雅俗共賞。

藿谷洞長不足百步,南瀕大箕河,北出後街。南北街口各有一爐正火,助“四奶奶”看烘火,供祖母或母親們熏手熏腳。母親們和奶奶們常常會搬個椅子或板床,穩穩噹噹坐在正火跟前,一邊烤火,一邊陪着“四奶奶”看燈。

俗話說三個女人一台戲,但那時候的女人卻沒戲,既不調笑,也不說穢語。所有的奶奶和母親們絕不開玩笑的。她們的心那時候是非常虔敬的,是懷着無限神聖的。她們心中的“四奶奶”就在燈下,護佑着他們的兒孫。不管誰家裡的孩子,都是四奶奶殿里來的,都是四奶奶的侍兒。四奶奶是鄉村女人們心中最可愛,最可敬,最可親,最嚴肅的一位女神。她們會拿五色紙給四奶奶糊些被子褥子、衣裳、鞋襪。四奶奶應該是三寸金蓮,因此那鞋也糊得小辣椒一樣,很俊,很俏。除了鋪、蓋、穿、戴,還要給四奶奶上“銀兩”,即錫箔之類。

供品是“桃”。面蒸的,洋紅洋綠點綴些花恭弘=叶 恭弘,給人的感覺是喜氣盈盈,這叫“壽桃”,是寶塔狀的。還有“油桃”,偏形的,卧式的。蒸熟之後,在鍋里放點油,燒熱,把“桃”背在油鍋里擦一擦,又香又好看。最簡單最聖潔的供品是“香米茶飯”。名字看上去太詩意了!把小米炒開花,放點水,熗一下,用酒盅一盅一盅扣在盤子里,像一個個小沙包。

祖母和母親們在用心觀燈。那綠褲紅裙之間所包含的,那玻璃罩所罩的,是仙緣,是仙機。她們在和“四奶奶”一起度過元宵,她們在恬靜中陪伴着她們心中的“四奶奶”。她們也知道“四奶奶”不是世界上最高和最有權威的神或仙,但“四奶奶”最喜歡孩子,是一位慈祥的奶奶。為了孩子,為了孫子,她們願意那麼陪着。那樣陪着,她們的心裏是安定的,是穩貼的。她們的內心永遠有那麼一團神聖。“漠虛靜以恬愉兮,澹無為而自得。聞赤松之清塵兮,願承風乎遺則。貴真人之休德兮,美往世之登仙。”這才是她們的真心,雖然她們並未讀過《楚辭》。

掛起燈的時候,能有雪花飄落,那是再美不過了。那叫“瑞雪兆豐年”,最富詩意。

到此,年就算完全結束了。“九九加一九,耕牛遍地走”,到一心一意種莊稼的時候了。

(作者:卓 然,系山西省晉城市作協名譽主席)

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

吃糖瓜、祭灶神……小年如何過出儀式感?_網頁設計公司

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

“二十三日過小年,差不多就是過新年的‘綵排’。”著名作家老舍在《北京的春節》中,曾記錄過一系列有關春節的民俗,其中就提到了小年,字里行間透出了濃濃的年味兒。

不過,小年的日期並不一致,有的是在臘月二十三,有的是在臘月二十四……但一般而言,小年都被當做是春節的序曲,等那首“過年謠”唱起來,人們就開始按部就班做準備,直到迎來熱熱鬧鬧的中國年。

小年日期不相同

小年是一個傳統節日,也被稱為謝灶、祭灶節、灶王節等。由於各地風俗,“小年”有着不同的概念,日期也不盡相同。

在古代,過小年有“官三民四船五”的傳統,也就是說,官家的小年是臘月二十三,百姓家的是臘月二十四,而水上人家則是臘月二十五。還有的地方把“臘月廿四”和“除夕前一夜”都稱為小年。

關於小年,還有一首“過年謠”,忙碌中自帶喜慶:“二十三,捏糖瓜;二十四,掃屋子;二十五,凍豆腐;二十六,燉豬肉;二十七,殺肥雞;二十八,把面發;二十九,蒸饅頭;三十上炕包餃子。”

不同地區的過年謠,內容會略有差別。但總體來說,都是表達了一種辭舊迎新、迎祥納福的美好心愿。

過小年為啥要“祭灶”?

小年被視為過年的開端,也自然會受到格外重視,祭灶便是期間一項重要的民俗活動。

灶神是中國古代神話傳說中的司飲食之神。也有傳說稱,灶王爺原本是一介平民,叫做張生。他成家后終日花天酒地,終於淪落到以乞討為生的地步。有一天,他恰好乞討到前妻家,羞愧難當,一頭鑽到灶鍋底下燒死了。

玉帝知道后,認為張生還有羞恥心,又是在鍋底死去,就把他封為灶王,每年臘月二十三、二十四上天“彙報”一家人的善惡,大年三十再回去。於是,漢族民間就有了“小年”祭灶的習慣,祈求來年平安順遂。

宋代的范成大在《祭灶詩》中說:“古傳臘月二十四,灶君朝天欲言事……杓長杓短勿復雲,乞取利市歸來分。”這首詩很形象地說明了古代有關祭灶的一些風俗習慣。

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

灶王爺畫像一般貼在灶台附近,祭灶時要把關東糖用火融化,塗在灶王爺的嘴上,這樣他就不能在玉帝面前說壞話。還要把舊畫像揭下來,再和用稻草紮成的“馬”一起燒掉,象徵著送灶神上天,也叫“辭灶”。之後,新年後再把新畫像貼上,迎回灶神。

除了糖瓜,這時還會吃些啥?

提到小年的飲食,糖瓜自然是標誌性食品之一。除了用來祭灶外,過去也是人們口中的美食。

糖瓜一般用黃米和麥芽熬制而成。這種糖有兩種形狀,長條的叫“關東糖”,扁圓的叫“糖瓜”,吃起來滿口生香,別有風味。

舊日,农民們一般到了隆冬季節沒什麼農活時才能休息,那時從臘月二十三到正月十五甚至是整個正月都是“年”,過年自然要吃餃子,時間久了,吃餃子也成了小年的習俗。

晉東南地區,流行小年吃炒玉米的習俗,民諺有“二十三,不吃炒,大年初—一鍋倒”的說法。人們喜歡將炒玉米用麥芽糖粘結起來,冰凍成大塊,咬上一口,又酥又脆。

剪窗花、掃房子……過年準備工作有哪些?

過了小年,離着春節只剩下不到十天時間了。剪窗花、貼對聯、貼年畫……過年的準備工作會更加熱鬧。

例如,各家各戶都會認真打掃房子、清除灰塵,窗明几淨迎接新年。據說,小年也叫掃塵日,源於堯舜時期的“掃年”習俗,原本是古代人民驅除病疫的一種儀式。

在所有準備過年的活動中,剪貼窗花是最流行的。窗花的內容可以是喜鵲登梅、燕穿桃柳、蓮(連)年有魚(餘)、和合二仙等,也有各種戲劇故事。

過去,臘月二十三后,家家戶戶要蒸饅頭,特別要製做一個“大棗山”來供奉灶君,饅頭也可以有許多造型,往往是大家一展靈巧手藝的好機會。

俗話說,“有錢沒錢,剃頭過年”。在此期間,大人、小孩都要洗澡、理髮,一身清爽迎接新年,人們認為,這樣也有“辭舊迎新”的意味。

其實,小年並不只是一個簡單的日期,重要的是它作為春節的前奏,此後的一系列民俗活動都強調了“參與感”,也是在這個過程中,年味越來越濃了。(記者 上官雲)

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

廣州大劇院2020年好戲不斷_包裝設計

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

廣州大劇院2020演出季發布會近日在廣州大劇院舉行。發布會介紹了2020廣州大劇院的重點項目,今年廣州大劇院在歌劇、音樂劇、戲劇戲曲及舞劇等陣營上將繼續上演眾多重磅作品,演出陣容更是星光熠熠,濮存昕、徐帆、靳東、韓雪、阿雲嘎、鄭雲龍等實力派演員都將帶着自己的舞台作品登陸廣州大劇院。

今年將開啟“央地合作”

2020年,廣州大劇院迎來開業十周年。據介紹,十年來,有1300多个中外藝術團體和32000多名藝術家先後登上廣州大劇院的舞台,帶來了3900多場精彩表演和1800多場藝術活動。同時,廣州大劇院充分利用中演院線以及絲綢之路國際劇院聯盟等國內、國外的演藝平台和渠道,积極推動廣東演藝院團、優秀演藝項目“走出去”。2018年12月,廣州芭蕾舞團赴馬耳他瓦萊塔地中海會議中心演出《芭蕾詩篇》;2019年8月,廣州芭蕾舞團編創的現代芭蕾舞劇《布蘭詩歌》和民族芭蕾舞劇《洛神》登陸美國紐約林肯中心大衛·寇克劇院;2020年,廣州歌舞劇院的民族舞劇《醒·獅》將赴美國波士頓博赫中心舒伯特劇院、華盛頓肯尼迪中心歌劇院上演。作為廣州、廣東的大劇院,廣州大劇院也堅持紮根本土,弘揚嶺南文化,十年來,劇院累計上演廣東本土優秀劇目300餘場。

今年,廣州大劇院將開啟文化領域“央地合作”新征程,作為中演院線的旗艦劇院,依託院線資源與故宮博物院開展合作,開發聯名文創產品,開辦系列講座,讓一南一北兩座文化殿堂迸發新火花。

發布會現場還舉行了中演院線廣州大劇院戲劇藝術中心授牌儀式,邀請著名劇作家唐棟、著名戲劇導演傅勇凡、著名舞美設計師秦立運等業內大咖出謀划策,加強原創劇目生產。中演院線執行董事、總經理,廣州大劇院董事長張利表示:“廣州大劇院在向生產型劇院邁進。以前有《馬可·波羅》,之後,創作會成為常態。”此外,廣州大劇院還聯合星海音樂廳推出貝多芬誕辰250周年紀念系列,組織全球超過500名藝術家,呈現貝多芬作品的大全集。

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

2020年好戲提前看

歌劇方面,今年廣州大劇院又有大動作,原創歌劇《馬可·波羅》 院慶十周年特別版將上演。此次邀請到人氣與實力兼具的王凱、洪之光、劉彬濠等新生代演員加盟,還有田浩江、周曉琳、宋倩、劉穎等蜚聲海內外的歌唱家坐鎮,同時聯手廣東珠影樂團和深圳歌劇舞劇院,為“大灣區歌劇孵化基地”的初試啼聲打下堅實基礎。發布會上,洪之光獻演劇中詠嘆調,提前讓觀眾感受到該劇的魅力。洪之光透露,其在劇中飾演文天祥,這個角色圓了自己想演中國原創歌劇的願望。此外,中國歌劇舞劇院的民族歌劇《小二黑結婚》和德國波恩歌劇院的貝多芬誕辰250周年獻禮歌劇《費德里奧》也備受期待,歌劇《費德里奧》是貝多芬偉大的作品之一,該劇有關於愛、生命、自由的表達,也有對婚姻中美好與聖潔的讚美。

戲劇方面,今年廣州大劇院也將迎來多部重磅作品,包括國家大劇院與廣州話劇藝術中心聯合製作原創話劇《林則徐》、北京人民藝術劇院話劇《天下第一樓》、俄羅斯瑪斯特卡雅劇院話劇《靜靜的頓河》以及北京當代話劇團《麥克白》、莎翁經典《哈姆雷特》等。

音樂劇陣營更是流量與明星雲集,韓雪、劉令飛領銜主演年度爆款音樂劇《白夜行》。中文原創音樂劇《圖蘭朵》的製作由中外頂級團隊聯合打造。音樂戲劇《叢林之書》由法國最重要劇院之一的巴黎城市劇院特邀享譽世界的先鋒戲劇大師羅伯特·威爾遜執導,超現實民謠姐妹花可可羅希為該劇創作了獨特音樂和歌曲,以全新的形式再現奇幻世界。

舞蹈方面,楊麗萍作品《十面埋伏》備受期待,該劇以中國舞蹈為主調,融合行為、裝置藝術、民樂及傳統戲劇等綜合藝術語言創造了“舞蹈劇場”,“風暴舞者”胡沈員、朱鳳偉將加盟演繹。大型原創民族舞劇《花木蘭》同樣陣容強大,飾演花木蘭的是荷花獎金獎獲得者郝若琦。本土兩大原創舞劇《沙灣往事》《醒·獅》也將再次登台。

戲曲方面,去年以一出《穆桂英挂帥》在廣州掀起了戲曲熱的的國家京劇院今年將帶來《龍鳳呈祥》《滿江紅》《鳳還巢》三部京劇作品,繼續由於魁智、李勝素領銜主演。此外,今年仍有多個世界名團的音樂會登陸廣州大劇院,包括巴黎室內樂團與別列佐夫斯基音樂會、波恩貝多芬交響樂團貝多芬交響曲音樂會等,精彩不斷,驚喜不斷。(記者 徐紹娜)

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

※產品缺大量曝光嗎?你需要的是一流包裝設計!

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

一篇文章講透Dijkstra最短路徑算法_網頁設計

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

Dijkstra也叫迪傑斯特拉,是典型最短路徑算法,計算一個起始節點到路徑中其他所有節點的最短路徑的算法和思想。在一些專業課程中如數據結構,圖論,運籌學等都有介紹。其思想是一種基礎的求最短路徑的算法,通過基礎思想的變化可以解決很多複雜問題,如導航線路,動態規劃等。

Dijkstra 算法思想介紹

如下圖是一個多節點,多路徑圖。下面以該圖為例子講解dijkstra算法尋找最短路徑的過程。

以A點為起始點,求A點到其他點 B C D E F 5個點的最短路徑,最後得出A到其他點的最短路徑。

因為要求A到其他5個點的最短距離,所以構造一個數組記錄A到B C D E F 5個點的路徑距離。約定:

  • 如果A能夠直接達到節點,則使用路徑長度即權值作為其距離
  • 如果A節點不能直接達到節點則使用無窮大表示A到該點距離。
  • 任何點到自身都為0

那麼在最開始時,A點到圖中所有點的距離數組如下:

A B C D E F
0 10 無窮大 4 無窮大 無窮大

dijkstra的算法思想是從以上最短距離數組中每次選擇一個最近的點,將其作為下一個點,然後重新計算從起始點經過該點到其他所有點的距離,更新最短距離數據。已經選取過的點就是確定了最短路徑的點,不再參与下一次計算。

可能看到這裏你完全不明白dijkstra算法的思想,心裏可能想:這是說的人話嗎?不要緊,如果算法一句話就能解釋清楚,那就不會出現那麼多算法書了。下面我們就從實際的選取過程中理解這個思想的精髓。

第一次選取

構建好的數組是這樣的:

A B C D E F
0 10 無窮大 4 無窮大 無窮大

第一步選取該最短路徑數組中值最小的一個點。因為A點到本身不需要參与運算,所以從剩下的點中選擇最短的一個是D。
第二步以A-D的距離為最近距離更新A點到所有點的距離。即相當於A點經過D點,計算A到其他點的距離。

A-A : 0
A-B : A-D-B:6
A-C : A-D-C:19
A-D : A-D:4
A-E : A-D-E:10
A-F : A-D-F:去窮大

A B C D E F
0 6 19 4 10 無窮大

將現在A到各個點的距離和之前的比較,到相同點取最小值。更新了B C E的距離,得到如下新的最短距離數組:

A B C D E F
0 6 19 4 10 無窮大

同時現在A D兩點已經計算過,不參与下面的計算。

第二次選取

第二次選取的數組為第一次中更新過最短距離的數組

A B C D E F
0 6 19 4 10 無窮大

第一步:因為A D 不參与選取,所有從剩下的點中選取最近距離是點B
第二步:以B為最新點,更新最短數組

A-A : 0
A-B : A-D-B:6
A-C : A-D-B-C:14
A-D : A-D:4
A-E : A-D-B-E:12
A-F : A-D-B-F:無窮大

A B C D E F
0 6 14 4 12 無窮大

對比現在的最短距離和上一個數組的距離,到相同節點取最小的,C點由19更新成14,E點走A-D-E為10,距離更短所以不更新(敲黑板,這個重要),得到如下數組:

A B C D E F
0 6 14 4 10 無窮大

此時B點加入最短路徑範圍中。

第三次選取

上一步得到的數組為:

A B C D E F
0 6 14 4 10 無窮大

第一步:選取除了A B D節點之外的剩餘節點中最短節點,為點E
第二步:以E點為最新節點,更新最短路徑數組

因為在上一部中計算達到E點的距離時沒有更新距離,A-D-E 為10 最短,所以更新E點到B C F點的距離時走的路徑是A-D-E。注意這裏的最短距離有對應的路徑,選擇最小值就是選擇最短距離。

A-A : 0
A-B : A-D-B:6
A-C : A-D-E-C:11
A-D : A-D:4
A-E : A-D-E:10
A-F : A-D-E-F:22

A B C D E F
0 6 11 4 10 22

對比現在的最短距離和上一個數組的距離,到相同節點取最小的,更新C點走A-D-E-C 為11,比之前的A-D-B-C14距離更近,更新到F點距離,得到如下數組:

A B C D E F
0 6 11 4 10 22

此時E點加入最短路徑範圍中。

網頁設計最專業,超強功能平台可客製化

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

第四次選取

A B C D E F
0 6 11 4 10 22

第一步:選取除了A B D E節點之外的剩餘節點中最短節點,為點C
第二步:以C點為最新節點,更新最短路徑數組

A-A : 0
A-B : A-D-B:6
A-C : A-D-E-C:11
A-D : 4
A-E : A-D-E:10
A-F : A-D-E-C-F:16

A B C D E F
0 6 11 4 10 16

對比現在的最短距離和上一個數組的距離,到相同節點取最小的,更新到F點距離,可以得到如下數組:

A B C D E F
0 6 11 4 10 16

第五次選取

A B C D E F
0 6 11 4 10 16

第一步:選取除了A B C D E節點之外的剩餘節點中最短節點,也就是最後一個節點:F
第二步:以F點為最新節點,更新最短路徑數組。由於F點是最後一個點,所以也不用更新數組,目前的數組就是所求數組
將F點加入最短路徑範圍中,此時所有的點都加入了最短路徑範圍,也就是說A點到所有點的距離都找到了。最總得出的距離值為:

最終得到的結果為:

A B C D E F
0 6 11 4 10 16

最終結果

相應的A點到所有點的最短路徑走法最終得到的結果為:

A B C D E F
0 6 11 4 10 16

A-A:0
A-B : A-D-B:6

A-C : A-D-E-C:11

A-D:4

A-E:A-D-E:10

A-F:A-D-E-C-F:16

算法總結

Dijkstra算法作為求最短路徑的經典算法,個人理解為算法提供了一種思想,每走一步都是找到最短的路徑,並且每走一步都實時更新所有距離,保證每次都選擇最短路徑。

python實現Dijkstra

將以上的過程使用python來實現。
首先總結一個Dijkstra算法的核心思想,分成兩步走:

  1. 構造一個最短路徑數組,每次找到數組中未訪問的節點里最小的點
  2. 以上一步的節點為最新節點,更新起始點到所有點的距離

使用python就是實現這兩步即可

數據準備

二維矩陣

如何描述一個圖呢?通常有兩種方式,分別是:十字鏈表和二維矩陣。因為二維矩陣更加直觀,所以選擇二維矩陣。

將上面的圖描述成一個二維矩陣

無窮大使用MAX = float('inf')表示,該數值是python中表示無窮大的一個值。

這個二維矩陣真正直觀之處在哪裡呢?是能夠看到任意一個點到其他點的距離。如想看D點到其他點的距離,就是:

在我們的算法兩步走中第二步要更新A點經過某點到其他點的距離,正是使用了這個特徵。

MAX= float('inf')

matrix = [
    [0,10,MAX,4,MAX,MAX],
    [10,0,8,2,6,MAX],
    [MAX,8,10,15,1,5],
    [4,2,15,0,6,MAX],
    [MAX,6,1,6,0,12],
    [MAX,MAX,5,MAX,12,0]
    ]

最短路徑數組

在上面講解算法過程中有一個重要的的最短路徑數組,不斷更新該數組直到所有的點都被訪問到。使用python語言,構造該數組:

distance = [MAX] * len(matrix)

len(matrix) 實際上算出的圖的點的個數。初始化時所有的節點都是不可達。

在算法過程中還有一個重要的數組,並沒有體現出來,但是在python計算時也很重要,那就是訪問過的點。每一次訪問之後就要將訪問過的點加入到該數組中,這樣做是為了避免重複訪問。

used_node = [False] * len(matrix)

初始化時認為所有點都沒有訪問到

代碼實現



MAX= float('inf')

matrix = [
    [0,10,MAX,4,MAX,MAX],
    [10,0,8,2,6,MAX],
    [MAX,8,10,15,1,5],
    [4,2,15,0,6,MAX],
    [MAX,6,1,6,0,12],
    [MAX,MAX,5,MAX,12,0]
    ]


def dijkstra(matrix, start_node):
    
    #矩陣一維數組的長度,即節點的個數
    matrix_length = len(matrix)

    #訪問過的節點數組
    used_node = [False] * matrix_length

    #最短路徑距離數組
    distance = [MAX] * matrix_length

    #初始化,將起始節點的最短路徑修改成0
    distance[start_node] = 0
    
    #將訪問節點中未訪問的個數作為循環值,其實也可以用個點長度代替。
    while used_node.count(False):
        min_value = float('inf')
        min_value_index = 999
        
        #在最短路徑節點中找到最小值,已經訪問過的不在參与循環。
        #得到最小值下標,每循環一次肯定有一個最小值
        for index in range(matrix_length):
            if not used_node[index] and distance[index] < min_value:
                min_value = distance[index]
                min_value_index = index
        
        #將訪問節點數組對應的值修改成True,標誌其已經訪問過了
        used_node[min_value_index] = True

        #更新distance數組。
        #以B點為例:distance[x] 起始點達到B點的距離,
        #distance[min_value_index] + matrix[min_value_index][index] 是起始點經過某點達到B點的距離,比較兩個值,取較小的那個。
        for index in range(matrix_length):
            distance[index] = min(distance[index], distance[min_value_index] + matrix[min_value_index][index])

    return distance




start_node = int(input('請輸入起始節點:'))
result = dijkstra(matrix,start_node)
print('起始節點到其他點距離:%s' % result)

結果:

請輸入起始節點:0
起始節點到其他點距離:[0, 6, 11, 4, 10, 16]

簡單總結

學習python實現Dijkstra重要的地方有幾點:

  1. 數據構造 二維矩陣表示圖
  2. 圖的訪問方式 更新最短路徑數組的過程無非就是分別比較二維矩陣數組中某一行的值和最短路徑數組的值

熟悉這樣的處理方式,再有類似的算法也能找到解決的思路。例如一個二維矩陣,從起始點開始只能走向下的相鄰的元素,求達到某點的最短路徑。
希望通過該篇文章,能夠深刻理解Dijkstra算法,做到心中有數,手中有活。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

2020本科校招-從小白到拿到30k offer的學習經歷_貨運

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

本文是個人的2020年年中總結

還有十幾天就要畢業,面臨着身份從學生到互聯網社畜的轉變,未來的一切捉摸不定,但凡心中萬千情緒,也只能「但行好事,莫問前程」。

介紹下博主背景:計算機本科大四,剛進大三時還是個沒有實習、沒有項目經歷的小白,
經過一年時間的刻意練習[deliberate practice],最後在校招中拿到了一些大廠offer,
標題中30k的offer已經拒掉了,有了更好的去處,只是用來舉例。

文章同步自 https://github.com/mio4/interview

0x0 前期調研

以下經驗根據個人的經驗來談,有一定的局限性,僅供參考。

1. 為什麼不選擇算法

計算機專業的就業方向很多,對於應屆生來說,工種大概可以分為移動端、前端、後端、算法、運維、測試。

由於近年來ML和DL的快速發展,特別是16年AlphaGo以及視覺檢測、自然語言處理方向的進展,人工智能方向成為了繼大數據之後的又一個學術熱點。

周圍接觸到的同學,大部分集中於算法和後端方向。我最開始就是準備深入後端方向,並且決定本科直接就業。但是我有兩點糾結:

  1. 後端是否不如算法?
  2. 對於後端,本科學歷是否適合職業發展預期?
1. 本科生以算法作為工作目標難度太高,和字節跳動的面試官交流,目前字節AI Lab的成員組成是大部分碩博,以及少部分優秀的本科生(acm區域金作為參考)
2. 算法需要的三點:實習、競賽和paper,我都沒有信心和諸多轉專業的工科碩士抗衡
3. 個人對於算法沒有強烈興趣,發展空間不明朗,行業未來搭上5G的車,可能會有比較大的想象空間,風險和收益並存。

參考

2021 校招算法崗, 勸退還是繼續

如何看待 2020 屆校招算法工程師崗位求職人數遠大於招聘崗位的現象?

2. 為什麼選擇Java後端

如果認定了後端,後端語言這麼多,如何選擇適合自己的方向?

業界有一種說法:『語言只是工具,數據結構和算法是解決問題的根本』。這種說法本身是沒有錯的,但是從普遍來看,本科生從有工作的想法到秋招,只有(或者不到)一年的時間,泛泛了解多個方向不如深入特定方向,更有助於求職。

後端主要的開發語言有JavaC++PythonPHPGo,PHP目前除了百度和騰訊部分部門之外,使用範圍不廣,不予討論。另外,Golang作為高併發場景的常見支持,在雲計算領域使用比較廣泛。

互聯網和其他資本市場沒有本質區別,既然是市場,就存在供求關係。 對於算法這種供遠遠大於求的情況,就是買方市場,賣方(求職者)需要更好的產品(技能)來獲得同等的價格。對於應屆生來說,找到一個良好的買方市場,更加有利。

調研基於兩個預設條件:

  1. 崗位數目越多,市場的需求越大。
  2. 高薪崗位越多,該方向的發展前景越大。

我們參考www.lagou.com,互聯網大部分獵頭或者對外招聘崗位都會發布在上面,有一定的參考價值。

地點選擇北京,薪資範圍選擇25~50k/month。

Java

(1)職位500+,可以看出需求缺口很大

(2)除此之外,Java生態系統最為完善,e.g. Spring全家桶。

(3)Java是電商網站的技術首選,阿里、京東、拼多多等電商公司的技術棧。

Python

Python主要是作為腳本語言,適用於運維開發和算法崗位,目前在字節跳動使用比較廣泛,可以看出崗位相對較少。適合作為輔助開發語言,畢竟寫腳本算得上是程序員的必備技能了。

C++

C++是遊戲開發(其次還有lua)和系統開發的首選語言,但是因為上手成本比較高(指針和內存管理)以及需要對操作系統深入了解,市場崗位需求也不如Java,所以不推薦。不過如果有acm或者信息競賽基礎,也不成問題。

0x1 秋招準備

隨着互聯網從業人數越來越多,競爭也呈現白熱化。科班學生不僅要和同專業同學競爭,也會感受到轉專業同學(大部分是碩士)帶來的壓力。參考同系講師的數據,我航15年時6系本科同學比較容易就能去BAT,到了19年BAT頭條對於大部分同學不是想進就進了。

從宏觀意義上來看招聘,企業需要找到適配崗位的員工,學生需要謀取職業生涯的第一份工作。對於學生來說,秋招和高考在本質上是相似的,都是一種選拔,需要證明自己的能力比別人更強。

在準備的時候,定量的標準往往更加具有說服力,『我守望先鋒玩的很厲害』遠遠不如『我守望先鋒全國天梯4000分』,『我學習Java花了很多時間』不如『我閱讀了《Java核心思想》和《Effective Java》以及…,並且復現了書中的代碼,在github倉庫xxx』。

我是從2018.8月下旬(進入大三)開始準備,到找到工作總共花了一年的時間,基本上分為三個時期:

  1. 2018.8 – 2018.12 :基本素質培養
  2. 2019.1 – 2019.8 : 百度智能雲實習
  3. 2019.7 – 2019.8 : 秋招提前批

整個秋招的核心思路 = 基礎+實習+項目

1. 基本素質

第一次認清自己的水平,是在大二下學期結束的暑假。當時投了百度暑期實習,毫無準備的前提下,只用了20分鐘就掛在了一面。

面試慘跪之後,我分析了自己的處境,當時的我GPA排名40%,沒有參加過ACM程序競賽,沒有實習經驗,除了OO課程,沒有寫過”大型”項目。從編程能力上看,大一數據結構60分醬油飄過,算法課程也限於理論,只能說能夠應付考試不掛科,Java只會語法,web框架更是沒有項目經驗。

這時候的我深刻意識到自己在編程方面還是個在新手村找不到北的green hand,記得剛上大一的時候,哪位神仙說我校混的差躺着也能去BAT?夢想很美好,現實很骨感。

相對於隔壁北郵,我航更加註重學生的深造(校內讀研和出國留學),對於工作方面的指導甚少。我大部分的時間都是一個人準備,所以經常陷入迷茫orz,這期間主要的問題是:

  1. 選擇什麼方向? (前面已經對市場進行了就業分析)
  2. 要學習什麼內容?(算法太高級了,玩玩可以,找工作水平不夠)
  3. 要學到什麼程度,才能找到實習/工作?

整個秋招的過程,就是我解決這三個問題的過程。

於是,首先我使用Hexo + Github.io 搭建了個人博客:mio4

博客有兩個作用:

  1. 能夠體系化自己的學習,方便複習
  2. 量化自己的學習成果,面試的時候,給面試官帶來好印象

其次開始有針對性的訓練自己的編程能力,

Java程序員的基本素質有:Java基礎(語法+JVM+多線程),Spring框架(SSM/SpringBoot,進階可以SpringCloud),算法能力(刷題)

2. BAT實習

找工作本身就是一個經驗和需求矛盾的過程:

找實習,認準BAT TMD,其中BATT(頭條)基本屬於一個檔;外企的話,ms/hulu/airbnb認可度也很高。

拿周圍同學舉例,即使是小廠開了一個月8、9k的實習補助,也不要因此放棄BAT的實習機會。

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

雖然近幾年百度已經明顯掉隊於BAT,但是就我個人體驗來看,外界對於百度的技術還是很認可的。對於實習來說,鍍金的效果完全夠用。【2020.5 update:目前應屆不滿一年沒有晉陞和普調資格,除非部門極好,否則不推薦轉正】

在2019.1月,托研二直系學長的福,內推經過三面通過了百度智能雲計算部的後端實習,面經:https://www.nowcoder.com/discuss/151829

到8月結束了實習,半年的實習時光對於工業屆有了更深入的了解。

3. 秋招面試

離開baidu主要有兩個原因:

  1. 工作內容不算滿意:在baidu的工作語言主要是php,大廠目前使用php的很少,不利於職業長期發展;另外實習負責的模塊也不算核心。
  2. 薪資不夠預期:baidu本科白菜價15k*15,每個月的工資扣掉五險一金和稅,只剩下11k,再加上北京動輒3、4k的房租,過於艱難。

因為花了接近一年時間準備,所以在提前批就結束了秋招,9月大部分企業剛開通秋招正式批通道前,我就歇了,真正準備校招的時間不足兩個月。【2020.5 update: offer這種東西當然是越多越好,去年的我太佛系了,騰訊、微軟、快手這些公司甚至沒投簡歷,現在覺得去年還是naive】

AI四小龍面過了兩家,都是sp | ssp,雲從當時沒聽說所以沒投,商湯提前批四道題A了三道解題報告,結果居然沒有不給面試機會Excuse me?

提前批收到了百度、字節跳動等大廠和一些獨角獸的offer,互聯網薪資保密,已經簽約的就不談具體待遇了。

拿個去年8月已經拒掉的獨角獸offer舉例:

0x2 其他經驗

1. 書籍推薦

國內csdn以及各種類似菜鳥教程的博客的知識系統往往支離破碎,不能深入學習。

看書能夠系統化學習知識,下面推薦我看過的一些書

  • 算法:

    • 《劍指Offer》
  • Java:

    • 《Java核心技術》,當字典看,不需要整遍過

    • 《Java併發編程實戰》 ,熟悉多線程

    • 《深入理解Java 虛擬機》,熟悉JVM必備

    • 《圖解Java多線程設計模式》,日本人寫的書,直觀易懂,強力推薦

    • 《Java 8實戰》

    • 《Effective Java》

  • Spring:

    • 《Spring實戰》
  • MySQL:

    • 《高性能MySQL》,主要是第五六章
    • 《Redis實戰》
  • 網絡:

    • 《圖解HTTP》
    • 《計算機網絡自頂向下方法》
  • 代碼規範:

    • 《重構》

2. 準備項目

對於在校學生來說,準備項目可能會面臨着無法下手的問題。

首先項目一般不是自己憑空想出來的,可以通過實驗室等方式接手比較成熟的方案。

我的操作是直接使用網課,比如慕課

選擇一個感興趣的,從0到1復現一個項目,完善自己的技術棧。

當然,imooc上大部分乾貨需要付費,學生黨如果沒錢可以考慮下面的方式:

蒲公英論壇:ipv6,好處是下載視頻不走校園網流量,有部分從imooc搬運的資源。

當然對於買不起資料的窮孩子,還是有灰色路徑可以『曲徑通幽』的,比如【已刪除】:盜版的論壇,基本上涵蓋所有imooc的視頻代碼資料,僅供參考。作為程序員,這裏盡量推薦支持正版。

3. 算法能力

面試必備:《劍指offer》 + LeetCode

LeetCode題目比較多,參考Hot/Top 100:

到秋招結束,刷題量不多,總共100左右,但是對每道題使用思維導圖進行總結,盡可能舉一反三:

4. 實習投遞

投遞實習的方式一般有:

  • [x] 熟人內推:最靠譜最快的方式,投遞簡歷到百度實習面試邀約只花了不到半天。

  • [x] 牛客網:響應時間一般,1~7天

  • [x] 實習僧app :響應時間一般,一般一周

  • [x] 官方通道:比如騰訊的暑期實習官方在線投遞。

4. 面試技巧

分公司分崗位的面經可參考:www.nowcoder.com

我的個人面經總結:https://github.com/mio4/learn-java/blob/master/Note/interview.md ,總共250頁的PDF,綜合個人經歷的所有面試,題目覆蓋率至少有50%。根據研二轉專業的同學反饋來看,作用很大。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

12萬起這些合資SUV品質都這麼好!居然還能降價達2.2萬?_網頁設計公司

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

4T渦輪增壓發動機,最大功率為140匹,最大扭矩為200牛米,匹配了一台6速手自一體變速箱,加速時初段反映還是不錯的,不過據市場反映,其運用的普利司通輪胎質量容易開裂。北京現代ix25指導價:11。98-18。68萬最大優惠價1。6萬元北京現代ix25自上市以來還沒有經歷過改款,在市場的受歡迎程度也僅次於本田XR-V車型,月銷量維持在1萬輛左右,現在全國市場普遍優惠1萬元左右,最優惠的是成都,達到了1。

年底買車優惠大,確實是很多消費者所想的,眼看着金九銀十這個最佳買車時期快過去了,可能接下來買車的時機還有下個月的廣州車展,以及元旦前經銷商為了沖銷量榜這幾個優惠期間了,但這一期介紹的這幾款10萬級小型SUV,常年的優惠也很大,最大達到了2.2萬元,如果與銷售多磨一磨價格,確實能夠在你的預算中再狠狠地砍個幾千塊,話不多說,趕緊往下看。

東風標緻2008

指導價:9.97-16.37萬

最大優惠價2.2萬元

標緻2008在全國各地具有1.5萬元以上的優惠,其中在上海地區的優惠最大,全系優惠達到了2-2.2萬元。

作為東風標緻的一款小型SUV產品標緻2008,車身最大的亮點就是擁有着全景天幕玻璃頂和超大視野的前擋風玻璃,透視感極佳,配合高品質的遮陽簾可以隔絕八成以上的紅外線,以及99%的紫外線,外觀設計上與208有些相似,都採用了極具運動風格的前臉設計,再加上車身的越野套件,整體形象十分硬朗。

動力上全新標緻2008搭載1.2T三缸的渦輪增壓發動機,以及1.6L自然吸氣發動機和1.6T渦輪增壓發動機,如今小排量渦輪發動機已經是發展趨勢,如果只是想用來代步,稍微兼容起家用空間的朋友們,可以入手1.2T發動機版本,百公里油耗為7L,安全配置齊全,帶ESp車身穩定和發動機啟停系統,經濟油耗低同時動力又跟得上,但後排空間稍微比較小,喜歡這款車的朋友可以去實測一下。

上汽通用 雪佛蘭創酷

指導價:10.99-14.99萬

最大優惠價2.0萬元

新款雪佛蘭創酷上市時,廠家主要是針對該車型進行一些配置的升級,創酷在全國平均有1萬元以上的優惠,在上海地區的優惠幅度最大,達到了1.6-2.0萬元。

雪佛蘭創酷採用了和別克昂科拉相同的平台打造,到售價卻比昂科拉便宜了挺多,創酷的車身外觀肌肉感比較強,線條簡潔,有着美系車的獨有味道,但整體的外觀還是比較中庸。

內飾風格依然非常地簡約,

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

中控台按鈕劃分清晰,比較容易上手,儀錶盤為單轉速表,右側則有行車電腦显示屏和數顯時速表。配置上全系標配ESp車身穩定系統,電動天窗,上坡輔助等等,而頂配還多了真皮座椅,倒車雷達,中控大屏等等。

動力上則搭載了1.4T渦輪增壓發動機,最大功率為140匹,最大扭矩為200牛米,匹配了一台6速手自一體變速箱,加速時初段反映還是不錯的,不過據市場反映,其運用的普利司通輪胎質量容易開裂。

北京現代ix25

指導價:11.98-18.68萬

最大優惠價1.6萬元

北京現代ix25自上市以來還沒有經歷過改款,在市場的受歡迎程度也僅次於本田XR-V車型,月銷量維持在1萬輛左右,現在全國市場普遍優惠1萬元左右,最優惠的是成都,達到了1.6萬元的優惠。

外觀設計上,北京現代ix25依舊延續了現代家族“流體雕塑2.0”的設計語言,相對於其他的現代車型,現代ix25顯得更加硬朗帥氣,非常符合國人的審美觀。

內飾同樣沒有太多花哨的設計,比較實用,硬塑料比較多,沒有其他點綴之物。新車標配了車身穩定系統,定速巡航,坡道輔助,倒車雷達,全景天窗,多功能方向盤,藍牙和一鍵啟動功能,確實性價比非常之高,動力則有1.6L和2.0L自然吸氣發動機和1.6T渦輪增壓發動機,能夠滿足對於動力不同需求的消費者,變速箱則涵括了6擋手動,6擋自動和6速雙離合變速器。

上汽大眾斯柯達Yeti

指導價:12.98-20.98萬

最大優惠價1.8萬元

新款斯柯達Yeti車型,取消了以前的老名字“野帝”,斯柯達Yeti在全國各地均為1萬元左右的優惠,其中上海地區的優惠還是最大,幅度達到了1.2-1.8萬元。

新款Yeti在外觀上並不會和老款野帝相差太多,只是在翼子板上多了些許裝飾件,更換了全新的輪圈,整體外觀看起來比較方正。

儀錶盤採用大眾傳統的雙錶盤+中央單色液晶显示屏,沒有什麼新意,後排座椅能夠前後移動,在空間靈活多變,比較實用。配置上大部分版本都配備了ESp車身穩定系統,全系標配電動天窗,動力上則搭配了1.6L自然吸氣發動機,1.4T和1.8T渦輪增壓發動機,變速箱則分別搭載5擋手動,7速雙離合和6速雙離合變速器。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

有人喜歡開手動擋嗎?手動擋車型也可以很高級你知道嗎?_網頁設計公司

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

另一方面,很多低端的車子,都會全系標配手動變速箱來壓低售價,像寶駿310、長安歐尚就是這樣的角色。所以就目前的經濟狀況來看,手動擋汽車的存在無論對於消費者還是廠商來說,還是有很大意義。假如不管好壞,只要最好玩呢。

據國外媒體消息,法拉利首席技術官Michael HugoLeiters接受採訪時表示,法拉利未來將不會再推出手動擋車型,因此,California成為了法拉利最後一款可以選裝手動變速箱的車型。

聽到了這個消息,作為法拉利車迷的不禁為之震驚,而震驚以後,更多的惋惜。在這個微涼的傍晚,不禁在思考,連跑車都開始放棄手動變速箱了,那這個變速箱還有存在的意義嗎?

雙離合真的比手動擋要快

要評價一個變速箱的性能,主要看它的換擋邏輯、平順性和換擋速度。從換擋邏輯和平順性來看,雙離合的表現主要跟廠商的調校有關,實際表現也是有好有壞的,而手動變速箱則主要跟個人的操作有關,新老司機的差別也很大。所以最具可比性的應該是兩種變速箱的換擋速度了,不說保時捷的pDK了,光大眾的DSG,官方就膽敢給出200毫秒的換擋速度,而手動擋呢?踩離合、從原擋位拔出、再推進新的擋位、松離合,即使是老司機,完成這一套動作的時間也不可能少於200毫秒吧?所以你要是單論換擋速度,雙離合真的完勝手動擋。

但手動擋有一個無敵的優點

手動變速箱造價低,手動擋車也一般比自動擋要便宜差不多一萬軟妹幣,所以對於部分消費者來說,想購得心儀的車輛又想省錢的話,手動擋就是最佳的選擇。對於廠商來說,手動擋則是一個市場工具,用來拉低新車的整體售價,博取眼球,

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

而實際上,很多手動擋的最低配車型,壓根就很難買到。像新思域的手動版本,都幾乎成了珍稀物品。另一方面,很多低端的車子,都會全系標配手動變速箱來壓低售價,像寶駿310、長安歐尚就是這樣的角色。所以就目前的經濟狀況來看,手動擋汽車的存在無論對於消費者還是廠商來說,還是有很大意義。

假如不管好壞,只要最好玩呢?

新手開手動擋一天下來,左腳都是要報廢的節奏,市區擁堵路段的頻繁換擋更是考驗司機手臂耐力和換擋技巧,手動擋的各種麻煩相信都是不言而喻的。那還有沒有在購車預算充足的情況下還願意購買手動擋汽車的人?答案是肯定的,就有一位這樣的朋友,他是手動版昂克賽拉的車主。還記得那天和他開車出去吃飯,隔壁車道也來了一輛昂克賽拉,右側車道的我們眼看前面修路必須要併入左側車道,但隔壁的車子絲毫沒有避讓的意思,緊急時候,豈能認慫?只見他踩下離合,一個之字型的手勢,馬上從5擋換下4擋,小昂轉速馬上飈至3500,緊跟一腳油門和及時的打方向,順利搶在鄰車前面進入左車道,揚長而去。

當然,並不是呼籲大家要怎樣的暴力駕駛,只是想讓大家知道,有些樂趣,只有手動擋能給你。它不是最好的,但一定是最好玩的。

總結:就像汽車會取代馬車那樣,舊事物總是會被更符合人們需求的新事物所取代,自動擋逐漸取代手動擋,這是個必然的趨勢。車企會繼續生產手動擋汽車,但也絕對不是因為部分人的情懷,相信手動擋並不會消失,就像在這個汽車工業發達的年代,在旅遊景點里不是還有人坐馬車嗎?本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

當老傳統成為新時尚_包裝設計

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

北宋年間,家家戶戶貼年畫已是一種風尚,也由此,木版年畫在歷史的長河中始終留存着時尚的記憶。當代,手工木版年畫被以非物質文化遺產的形態加以保護,同時,傳承和創新也成為木版年畫傳承人肩負的兩個重任。有這樣一群人,他們視木版年畫為中國民間藝術寶庫中的一顆璀璨明珠,一直走在挖掘、創新、變革的路上。又是新年將至,今年的木版年畫可以為人們帶來哪些新意?這些心心念念的新時代“年畫家”都在忙些什麼?

 設計師王全傑 :年畫新可能

楊柳青年畫周曆簽

新年將至,北京順義區的一所小院里,設計師王全傑剛剛忙完楊柳青年畫周曆簽——2020年“世象新語”周曆的預售,5000套周曆幾乎都已被單位預定出去。初次嘗試與楊柳青木版年畫合作,並能夠得到市場的認可,王全傑不斷回味。

2019年7月,王全傑在清華美院校友召喚下加入了年畫日新創作營,本着對於年畫的關注與喜愛,帶着輕鬆自在的心情,王全傑輕裝上陣了。

剛入營,兩件事讓王全傑心情無法平靜。首先創作營的主辦方將組織結構安排得相當縝密,全國11個木版年畫產地會自發組成一個個創作小組(注:最終實際組成了9個創作小組),年畫傳承人+插畫師+設計師+導師(清華美院和中央美院的教授和副教授)作為一個小組組合,王全傑成了楊柳青組組長。主辦方的安排是,經過一個階段的年畫知識學習,再經歷1個月的創作,然後做出創新作品和產品進行展覽,最後對社會公開創作成果。

在學習了年畫知識、深入年畫創作地區了解了年畫歷史后,王全傑原先以為會憑藉著豐富設計經驗輕鬆完成這份任務,但卻被厚重的傳統文化攪動了內心。“真是無處下手,年畫傳承人深陷其中很難改變,我們作為外來者,出於對老祖宗留下的這些寶物的敬畏,似乎也不敢隨便說、隨便做了。”王全傑說。

一個月的迷茫期過後,王全傑總結出年畫轉化的兩個難點:第一,作為傳承人,雖然擁有老祖宗留下的年畫模板、掌握着傳統的刻畫技藝,但是缺少產品轉化能力;第二,作為外來者——設計人員,雖有很強的設計能力,但究竟轉化成什麼是最大的難點。

王全傑在腦海中翻轉:做什麼樣的產品,怎麼能體現楊柳青風格,怎麼能有新意,怎麼能增加使用的體驗感、情景感,還得製造點小驚喜。

經歷了無數個靈感的沉浮,一次次被自己或小組成員否定,王全傑關於周曆的創意橫空出世。機緣巧合的是,這樣一個創意很快得到北京一家企業的認可,並有意出資支持該創意。王全傑信心倍增。

“創意有了,落實到產品時,第一版就被出資方否定了。”王全傑再次回到原點。利用楊柳青年畫元素設計的第一版周曆美觀,但缺少生活黏性、很難達到與使用者的互動。

年畫日新創作營楊柳青組設計師們再次前往楊柳青,找到楊柳青木版年畫國家級非遺傳承人霍慶順,聽霍慶順細數年畫民俗,並對其所擁有的與老百姓生活相關的藏品做了仔細研究。霍慶順老人一句話點亮了王全傑內心。

“你看踩高蹺,大多數人都只看到表面上的熱鬧,但很少有人知道這其中最主要的用意是祭拜藥王、祈福安康。進入年關,每一天都有特殊的意義……”這句話就像一把開啟創意之門的鑰匙,為迷途中的王全傑和設計師們打開了門:“周曆的基礎上加入日曆,在臘月的最後一周開始日曆倒計時。”

創意有了,楊柳青創作小組成員連續两天兩夜的頭腦風暴,將創意再次細化落實在產品上:在周曆基礎之上特別策劃的“過年日曆”,篇篇辣詞趣語,以當下語境直入現代生活,內置如意轉盤每天跟你靈犀互動。這些新意讓2020年“世象新語”周曆產品順利出版落地。

“我們設計師想給楊柳青木版年畫傳承者做出一個示範,讓他們看到傳統年畫與當代流行文化融合的更多可能性。為大眾提供一個日常使用載體,讓人們了解楊柳青木版年畫的豐富性和多樣性。”王全傑說。

故事記錄者恭弘=叶 恭弘萌 :回歸當代時尚

穀雨平台上的金華木板年畫

對傳統手工藝有着濃厚興趣的恭弘=叶 恭弘萌,探索傳統與當代文化融合的路徑用了5年時間,為了讓更多人了解傳統手工藝他組建了一個傳統手工藝視頻傳播平台——穀雨,這個平台免費為手工藝人們拍攝宣傳片。

※產品缺大量曝光嗎?你需要的是一流包裝設計!

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

“中國上下五千年,從來不缺故事,但卻沒有人會講這些中國故事,有太多我們為之自豪的傳統手工藝散落於四野。如果不能讓人看見,又何談傳承呢?”恭弘=叶 恭弘萌說。

恭弘=叶 恭弘萌畢業於中國美術學院視覺傳達系。他做過設計師,參与設計的項目有中國國際西湖博覽會、第21屆金雞百花電影節標誌等;在電視台工作了十幾年。為了追逐心中的夢想,放棄穩定的工作,恭弘=叶 恭弘萌創業成立了自己的文化公司。服務了幾百個商業客戶后,2015年他突然感覺工作生涯中所有的積累都應該為了達成他內心中一個夢想——用各種方式構築傳統手工藝從再現到再生的體系,讓傳統手工藝能夠回歸當代生活,成為新的時尚,至此,穀雨——傳統手工藝內容轉化平台宣告問世。

2019年,穀雨的木版年畫產品轉化項目“傳統節中國禮”主旨就是讓傳統節更中國、更時尚。“穀雨打造的內容轉化平台是要做傳統手工藝時尚品牌孵化池。”恭弘=叶 恭弘萌說。

穀雨的平台上金華木版年畫製作者黃菁菁就是一位不甘於讓傳統成為傳說的女性。

金華木版年畫,孕育於漢唐,形成於宋元,鼎盛於明清。浙江在五代吳越時期就是木版畫比較發達的地區,至宋代,金華已經是全國木版年畫的中心之一。

黃菁菁出身木版年畫世家,長大后並沒有從事年畫製作,而成為一名商人,在杭州開了一家文化公司,經營得紅紅火火。雖然她在商界收穫了成功,但內心深處無數次回憶起兒時父輩們製作木版年畫的場景、父親抱着她給她講《五子登科》年畫故事的場景……她對記憶中的年畫魂牽夢繞。

那段日子里,似乎總有一個聲音,在向她不停地召喚。她決定:回家,做木版年畫。

回鄉之時正是金華木版年畫最蕭條的時期,曾經有專家對金華木版年畫的萎縮倍感痛心,黃菁菁的回歸讓金華木板年畫再現生機。

說來容易,做起來才知道苦辣辛酸。首先需搜集老版與老畫,“尋回金華年畫的根”。剛開始的那幾年,黃菁菁一直奔波在路上,從南疆到北國。後來甚至還走出了國門,無論是老版還是老畫,黃菁菁的用語都是“請回來”。 就這樣,黃菁菁一點一滴地打造起金華木版年畫博物館和年畫製作體驗館。如今,她的年畫博物館中已收藏了60餘套老版、2000多幅老年畫,其中六成都是孤版。而她的年畫製作體驗基地成了中國第六個年畫製作基地,也是浙江唯一的年畫基地。

傳承人邰立平:讓年畫“火”起來

邰立平作品《方弼》

年關將至,鳳翔木版年畫代表性傳承人邰立平忙得不亦樂乎。

鳳翔年畫“始於唐宋,盛於明清”,早在600多年前的明初洪武年間,世代耕居於此的邰氏家族就已經開始從事年畫的生產了。邰立平是鳳翔木版年畫第20代傳人。他創辦了鳳怡年畫社,致力於對流散民間的古樣進行挖掘、整理、研究和複製,使這一古老民間傳統藝術得以傳承。

邰立平這一代傳承人經歷了年畫從興到衰整個過程。“隨着時代的轉變和人們生活方式的變化,之前人們張貼年畫的習俗正在慢慢地改變。雖然春節時張貼年畫的習俗還未消失,但也從張貼傳統手工印製的年畫轉變為張貼機器印刷品,這就對傳統的非遺手工年畫形成了很大衝擊。傳統的木版年畫就是反映人民生活的一部百科全書。近年來,隨着國家對傳統文化的重視,社會上對這項古老的傳承技藝越來越推崇和認可,出現了很多國潮風格的文創作品,這都是傳統非遺技藝的文化土壤。”邰立平說。

邰立平認為,當下是鳳翔木版年畫發展的最佳時機。“把木版年畫繼承下去,讓民間美術發揚光大”這是邰立平最大的心愿。為了能夠儘早實現自己的心愿,他分步驟推動自己的計劃。

首先需要找回失散的木刻版。從1978年改革開放到上世紀90年代的年畫,邰立平共復刻了400多套版,大概2000塊木版,把散落民間的老畫樣基本全部恢復。他還分別在1994年出版了手工裝訂的《鳳翔木版年畫選》第一卷、1997年出版了第二卷。目前第三卷的出版工作也在緊張進行中。

其次,在創作上,鳳翔年畫風格緊貼時代脈搏。1999年巴黎中國文化周,邰立平創作了活動吉祥物獅子滾繡球版畫,2008年奧運會時創作了福娃主題作品。鳳翔年畫的名氣越來越大。邰立平先後應邀在澳大利亞、德國、法國等多個國家以及國內各大美院和美術館參展,其作品也陸續被中國國家博物館和國內外200多家藝術學院與機構收藏。

再次,從經營的角度,邰立平也不斷進行着適應市場的改變。傳統年畫的尺寸和包裝都不適應現代都市人的需求,他們專門推出了適合張貼在城市單扇門上的小門神年畫,對年畫的包裝進行變革,更能使現代人喜歡和接受。

這些年邰立平一直在探索如何讓年畫走進千家萬戶,使老百姓能真正用起來。他參与非遺進校園活動;他和國內知名設計師合作,推出了雕刻時光日曆;和中國手藝網和雅昌合作,推出了鳳翔木版年畫年曆;和騰訊、京東合作,跨界進入手游領域,讓年輕人在玩遊戲時也能體驗國潮風。(鄭芋)

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

【匯總】 為園友寫的皮膚製作工具 awescnb_網頁設計

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

Awescnb, awesome cnblog.

簡介

可能許多初來乍到的新手會被博客園經典的風格勸退,或者您是一個老園友,需要為您的博客定製一些功能(例如宣傳公眾號,文章目錄、或者插入幾個捐助二維碼等等)而不想浪費太多時間。我製作這個小項目的目的是園友能夠輕易地構建一個博客園皮膚或完善您的博客頁面功能。它可以用來做以下三件事:

  1. 安裝: 在您的博客園安裝這個項目中已經集成的皮膚.安裝之後,可以快速切換其他皮膚.
  2. 創建: 快速創建一個的博客園皮膚,通過打包生成文件,供您使用.
  3. 分享: 快速創建一個博客園皮膚並將它貢獻給項目,園友就能夠切換到您的皮膚了.

視頻教程

今天周末錄製了一個簡單視頻教程,雖然我在搭建的文檔里有寫,希望它能幫您更容易上手。視頻從博客皮膚的安裝、切換、從零開始製作三個方面簡單展開。

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

您可能是非 web 前端從業者,視頻中用到的命令您可能會有疑惑,只需要下載 node並安裝到您的電腦就擁有了一個叫做 npm 的東西(node 包管理器),而視頻中使用的 npm xx 命令正是來源於此。

鏈接

  • 博客皮膚性能優化
  • 構建一個簡約博皮的過程
  • 用 webpack 玩轉博客園
  • 當前集成的 30+ 插件介紹
  • 手寫一個兼容博客園多編輯器的文章目錄插件

配置一覽

{
    // 基本配置
    theme: {
        name: 'reacg',
        color: '#FFB3CC',
        title: '',
        contentSize: 'mid',
        headerBackground: '',
        avatar: 'https://pic.cnblogs.com/face/sample_face.gif',
        favicon: '',
    },
    // 代碼高亮
    highLight: {
        type: 'atomOneDark',
        inDarkMode: 'atomOneDark',
    },
    // 代碼行號
    lineNumbers: {
        enable: true,
    },
    // github圖標
    github: {
        enable: true,
        color: '#ffb3cc',
        url: 'https://github.com/guangzan/awescnb',
    },
    // 碼雲圖標
    gitee: {
        enable: true,
        color: '#C71D23',
        url: 'https://gitee.com/guangzan/awescnb',
    },
    // 圖片燈箱
    imagebox: {
        enable: true,
    },
    // 文章目錄
    catalog: {
        enable: true,
        position: 'left',
    },
    // 返回頂部按鈕
    back2top: {
        enable: true,
        type: 'complex',
    },
    // 右下角按鈕列表
    tools: {
        enable: true,
        initialOpen: true,
        draggable: false,
    },
    // live2d模型
    live2d: {
        enable: true,
        page: 'all',
        agent: 'pc',
        model: 'haru-01',
        width: 150,
        height: 200,
        position: 'left',
        gap: 'default',
    },
    // 點擊特效
    click: {
        enable: true,
        auto: false,
        colors: ['#FF1461', '#18FF92', '#5A87FF', '#FBF38C'],
        size: 30,
        maxCount: 15,
    },
    // 評論輸入框表情
    emoji: {
        enable: true,
        showRecents: true,
        recentsCount: 20,
        showPreview: true,
        showSearch: true,
    },
    // 暗色模式
    darkMode: {
        enable: true,
        autoDark: false,
        autoLight: false,
    },
    // 音樂播放器
    musicPlayer: {
        enable: true,
        page: 'all',
        agent: 'pc',
        autoplay: false,
        volume: 0.4,
        lrc: {
            enable: false, // 啟用歌詞
            type: 1, // 1 -> 字符串歌詞 3 -> url 歌詞
            color: '', // 顏色
        },
        audio: [
            {
                name: '404 not found',
                artist: 'REOL',
                url:
                    'http://music.163.com/song/media/outer/url?id=436016480.mp3',
                cover:
                    'http://p2.music.126.net/cu1sEIDxXOJm5huZ3Wjs0Q==/18833534672880379.jpg?param=300x300',
                lrc: ``,
            },
        ],
    },
    // 隨筆頭圖
    postTopimage: {
        enable: true,
        // position: 'top', // position api 已經廢棄,使用 postbottomimage 代替
        fixed: false,
        imgs: [],
    },
    // 隨筆尾圖
    postBottomimage: {
        enable: false,
        img: '',
        height: '',
    },
    // 打賞
    donation: {
        enable: false,
        qrcodes: [],
    },
    // 個性簽名
    signature: {
        enable: false,
        contents: [],
    },
    // 二維碼
    qrcode: {
        enable: false,
        img: '',
        desc: '',
    },
    // 彈出公告
    notice: {
        enable: false,
        text: [],
    },
    // 首頁列表圖
    indexListImg: {
        enable: false,
        imgs: [],
    },
    // 頂部加載進度條
    topProgress: {
        enable: false,
        page: 'all',
        agent: 'pc',
        background: '#FFB3CC',
        height: '5px',
    },
    indexTimeline: {
        enable: false,
    },
    // 隨筆頁尾部簽名
    postSignature: {
        enable: false,
        content: [],
        licenseLink: '',
    },
    // 背景圖片或顏色
    bodyBackground: {
        enable: false,
        type: 'color',
        value: '',
        opacity: 1,
        repeat: false,
    },
    // 彈幕
    barrage: {
        enable: false,
        opacity: 0.6,
        colors: [
            '#FE0302',
            '#FF7204',
            '#FFAA02',
            '#FFD302',
            '#FFFF00',
            '#A0EE00',
            '#00CD00',
            '#019899',
            '#4266BE',
            '#89D5FF',
            '#CC0273',
            '#CC0273',
        ],
        barrages: [],
        indexBarrages: [],
        postPageBarrages: [],
    },
    // 圖表
    charts: {
        enable: false,
        pie: {
            title: 'My skills',
            data: {
                labels: ['JavaScript', 'css', 'Vue', 'React', 'wechat'],
                values: [40, 30, 20, 10, 20],
            },
        },
    },
    // 鎖屏
    lock: {
        enable: true,
        background: '',
        strings: [
            '<i>Powered by</i> webpack.',
            '&amp; Theme in awescnb',
            '快去自定義你的個性簽名吧~',
        ],
    },
    // footer鏈接
    links: [
        {
            name: 'awescnb',
            link: 'https://gitee.com/guangzan/awescnb',
        },
    ],
}

文檔

  • 配置皮膚
  • 配置皮膚
  • 創建新皮膚
  • 更新日誌

最後

起初我只是用 gulp(前端構建工具) 簡單製作了一個博客園皮膚供自己使用,後來越來越多的園友使用,我索性用 webpack 將它重構並和交流群里的小夥伴一起完善。現在它已經能夠完全勝任當前的工作了,enjoy!今後我也不再發布關於它的介紹隨筆,會花費精力寫其他前端相關內容。

有希望捐助的小夥伴不要再問我了,這個小項目非我一人所為,它不接受任何捐助。有任何建議或者問題都可以到交流群(541802647)里交流或者在項目倉庫提個 issue。再次感謝所有提供建議的小夥伴。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

網頁設計最專業,超強功能平台可客製化

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

透過 NestedScrollView 源碼解析嵌套滑動原理,Android View 的事件分發原理解析_貨運

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

NestedScrollView 是用於替代 ScrollView 來解決嵌套滑動過程中的滑動事件的衝突。作為開發者,你會發現很多地方會用到嵌套滑動的邏輯,比如下拉刷新頁面,京東或者淘寶的各種商品頁面。

那為什麼要去了解 NestedScrollView 的源碼呢?那是因為 NestedScrollView 是嵌套滑動實現的模板範例,通過研讀它的源碼,能夠讓你知道如何實現嵌套滑動,然後如果需求上 NestedScrollView 無法滿足的時候,你可以自定義。

嵌套滑動

說到嵌套滑動,就得說說這兩個類了:NestedScrollingParent3 和 NestedScrollingChild3 ,當然同時也存在後面不帶数字的類。之所以後面帶数字了,是為了解決之前的版本遺留的問題:fling 的時候涉及嵌套滑動,無法透傳到另一個View 上繼續 fling,導致滑動效果大打折扣 。

其實 NestedScrollingParent2 相比 NestedScrollingParent 在方法調用上多了一個參數 type,用於標記這個滑動是如何產生的。type 的取值如下:

    /**
     * Indicates that the input type for the gesture is from a user touching the screen. 觸摸產生的滑動
     */
    public static final int TYPE_TOUCH = 0;

    /**
     * Indicates that the input type for the gesture is caused by something which is not a user
     * touching a screen. This is usually from a fling which is settling.  簡單理解就是fling
     */
    public static final int TYPE_NON_TOUCH = 1;

嵌套滑動,說得通俗點就是子 view 和 父 view 在滑動過程中,互相通信決定某個滑動是子view 處理合適,還是 父view 來處理。所以, Parent 和 Child 之間存在相互調用,遵循下面的調用關係:

上圖可以這麼理解:

  • ACTION_DOWN 的時候子 view 就要調用 startNestedScroll( ) 方法來告訴父 view 自己要開始滑動了(實質上是尋找能夠配合 child 進行嵌套滾動的 parent),parent 也會繼續向上尋找能夠配合自己滑動的 parent,可以理解為在做一些準備工作 。
  • 父 view 會收到 onStartNestedScroll 回調從而決定是不是要配合子 view 做出響應。如果需要配合,此方法會返回 true。繼而 onStartNestedScroll()回調會被調用。
  • 在滑動事件產生但是子 view 還沒處理前可以調用 dispatchNestedPreScroll(0,dy,consumed,offsetInWindow) 這個方法把事件傳給父 view,這樣父 view 就能在onNestedPreScroll 方法裏面收到子 view 的滑動信息,然後做出相應的處理把處理完后的結果通過 consumed 傳給子 view。

  • dispatchNestedPreScroll()之後,child可以進行自己的滾動操作。

  • 如果父 view 需要在子 view 滑動后處理相關事件的話可以在子 view 的事件處理完成之後調用 dispatchNestedScroll 然後父 view 會在 onNestedScroll 收到回調。

  • 最後,滑動結束,調用 onStopNestedScroll() 表示本次處理結束。

  • 但是,如果滑動速度比較大,會觸發 fling, fling 也分為 preFling 和 fling 兩個階段,處理過程和 scroll 基本差不多。 

NestedScrollView

首先是看類的名字

 class NestedScrollView extends FrameLayout implements NestedScrollingParent3,
 NestedScrollingChild3, ScrollingView {

可以發現它繼承了 FrameLayout,相當於它就是一個 ViewGroup,可以添加子 view , 但是需要注意的事,它只接受一個子 view,否則會報錯。

    @Override
    public void addView(View child) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child);
    }

    @Override
    public void addView(View child, int index) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child, index);
    }

    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child, params);
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (getChildCount() > 0) {
            throw new IllegalStateException("ScrollView can host only one direct child");
        }

        super.addView(child, index, params);
    }

add view

對於 NestedScrollingParent3,NestedScrollingChild3 的作用,前文已經說了,如果還是不理解,後面再對源碼的分析過程中也會分析到。

其實這裏還可以提一下 RecyclerView:

public class RecyclerView extends ViewGroup implements ScrollingView,
        NestedScrollingChild2, NestedScrollingChild3 {

這裏沒有繼承 NestedScrollingParent3 是因為開發者覺得 RecyclerView 適合做一個子類。並且它的功能作為一個列表去展示,也就是不適合再 RecyclerView 內部去做一些複雜的嵌套滑動之類的。這樣 RecycylerView 外層就可以再嵌套一個 NestedScrollView 進行嵌套滑動了。後面再分析嵌套滑動的時候,也會把 RecycylerView 當作子類來進行分析,這樣能更好的理解源碼。

內部有個接口,使用者需要對滑動變化進行監聽的,可以添加這個回調:

    public interface OnScrollChangeListener {
        /**
         * Called when the scroll position of a view changes.
         *
         * @param v The view whose scroll position has changed.
         * @param scrollX Current horizontal scroll origin.
         * @param scrollY Current vertical scroll origin.
         * @param oldScrollX Previous horizontal scroll origin.
         * @param oldScrollY Previous vertical scroll origin.
         */
        void onScrollChange(NestedScrollView v, int scrollX, int scrollY,
                int oldScrollX, int oldScrollY);
    }

構造函數

下面來看下構造函數:

    public NestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs,
            int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initScrollView();

        final TypedArray a = context.obtainStyledAttributes(
                attrs, SCROLLVIEW_STYLEABLE, defStyleAttr, 0);
        // 是否要鋪滿全屏
        setFillViewport(a.getBoolean(0, false));

        a.recycle();
        // 即是子類,又是父類
        mParentHelper = new NestedScrollingParentHelper(this);
        mChildHelper = new NestedScrollingChildHelper(this);

        // ...because why else would you be using this widget? 默認是滾動,不然你使用它就沒有意義了
        setNestedScrollingEnabled(true);

        ViewCompat.setAccessibilityDelegate(this, ACCESSIBILITY_DELEGATE);
    }    

這裏我們用了兩個輔助類來幫忙處理嵌套滾動時候的一些邏輯處理,NestedScrollingParentHelper,NestedScrollingChildHelper。這個是和前面的你實現的接口 NestedScrollingParent3,NestedScrollingChild3 相對應的。

下面看下  initScrollView 方法里的具體邏輯:

    private void initScrollView() {
        mScroller = new OverScroller(getContext());
        setFocusable(true);
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
     // 會調用 ViewGroup 的 onDraw setWillNotDraw(
false); // 獲取 ViewConfiguration 中一些配置,包括滑動距離,最大最小速率等等 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); mTouchSlop = configuration.getScaledTouchSlop(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); }

setFillViewport

在構造函數中,有這麼一個設定:

setFillViewport(a.getBoolean(0, false));

與 setFillViewport 對應的屬性是 android:fillViewport=”true”。如果不設置這個屬性為 true,可能會出現如下圖一樣的問題:

xml 布局:

<?xml version="1.0" encoding="utf-8"?>
<NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="#fff000">
        <Button
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>
</NestedScrollView>

效果:

可以發現這個沒有鋪滿全屏,可是 xml 明明已經設置了 match_parent 了。這是什麼原因呢?

那為啥設置 true 就可以了呢?下面來看下它的 onMeasure 方法:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // false 直接返回
        if (!mFillViewport) {
            return;
        }

        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (heightMode == MeasureSpec.UNSPECIFIED) {
            return;
        }

        if (getChildCount() > 0) {
            View child = getChildAt(0);
            final NestedScrollView.LayoutParams lp = (LayoutParams) child.getLayoutParams();

            int childSize = child.getMeasuredHeight();
            int parentSpace = getMeasuredHeight()
                    - getPaddingTop()
                    - getPaddingBottom()
                    - lp.topMargin
                    - lp.bottomMargin;
            // 如果子 view 高度小於 父 view 高度,那麼需要重新設定高度
            if (childSize < parentSpace) {
                int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin,
                        lp.width);
                // 這裏生成 MeasureSpec 傳入的是 parentSpace,並且用的是 MeasureSpec.EXACTLY 
                int childHeightMeasureSpec =
                        MeasureSpec.makeMeasureSpec(parentSpace, MeasureSpec.EXACTLY);
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }

當你將 mFillViewport 設置為 true 后,就會把父 View 高度給予子 view 。可是這個解釋了設置 mFillViewport 可以解決不能鋪滿屏幕的問題,可是沒有解決為啥 match_parent 無效的問題。

在回到類的繼承關係上,NestedScrollView 繼承的是 FrameLayout,也就是說,FrameLayout 應該和 NestedScrollView 擁有一樣的問題。可是當你把 xml 中的布局換成 FrameLayout 后,你發現竟然沒有問題。那麼這是為啥呢?

原因是 NestedScrollView 又重寫了 measureChildWithMargins 。子view 的 childHeightMeasureSpec 中的 mode 是 MeasureSpec.UNSPECIFIED 。當被設置為這個以後,子 view 的高度就完全是由自身的高度決定了。

    @Override
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        // 在生成子 view 的 MeasureSpec 時候,傳入的是 MeasureSpec.UNSPECIFIED
        final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

比如子 view 是 LinearLayout ,這時候,它的高度就是子 view 的高度之和。而且,這個 MeasureSpec.UNSPECIFIED 會一直影響着後面的子子孫孫 view 。

我猜這麼設計的目的是因為你既然使用了 NestedScrollView,就沒必要在把子 View  搞得跟屏幕一樣大了,它該多大就多大,不然你滑動的時候,看見一大片空白體驗也不好啊。

而 ViewGroup 中,measureChildWithMargins 的方法是這樣的:

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

由於一般使用 NestedScrollView 的時候,都是會超過屏幕高度的,所以不設置這個屬性為 true 也沒有關係。

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

繪製

既然前面已經把 onMeasure 講完了,那索引把繪製這塊都講了把。下面是 draw 方法,這裏主要是繪製邊界的陰影:

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        if (mEdgeGlowTop != null) {
            final int scrollY = getScrollY();
       // 上邊界陰影繪製
if (!mEdgeGlowTop.isFinished()) { final int restoreCount = canvas.save(); int width = getWidth(); int height = getHeight(); int xTranslation = 0; int yTranslation = Math.min(0, scrollY); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || getClipToPadding()) { width -= getPaddingLeft() + getPaddingRight(); xTranslation += getPaddingLeft(); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && getClipToPadding()) { height -= getPaddingTop() + getPaddingBottom(); yTranslation += getPaddingTop(); } canvas.translate(xTranslation, yTranslation); mEdgeGlowTop.setSize(width, height); if (mEdgeGlowTop.draw(canvas)) { ViewCompat.postInvalidateOnAnimation(this); } canvas.restoreToCount(restoreCount); }
       // 底部邊界陰影繪製
if (!mEdgeGlowBottom.isFinished()) { final int restoreCount = canvas.save(); int width = getWidth(); int height = getHeight(); int xTranslation = 0; int yTranslation = Math.max(getScrollRange(), scrollY) + height; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || getClipToPadding()) { width -= getPaddingLeft() + getPaddingRight(); xTranslation += getPaddingLeft(); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && getClipToPadding()) { height -= getPaddingTop() + getPaddingBottom(); yTranslation -= getPaddingBottom(); } canvas.translate(xTranslation - width, yTranslation); canvas.rotate(180, width, 0); mEdgeGlowBottom.setSize(width, height); if (mEdgeGlowBottom.draw(canvas)) { ViewCompat.postInvalidateOnAnimation(this); } canvas.restoreToCount(restoreCount); } } }

onDraw 是直接用了父類的,這個沒啥好講的,下面看看 onLayout:

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        mIsLayoutDirty = false;
        // Give a child focus if it needs it
        if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {
            scrollToChild(mChildToScrollTo);
        }
        mChildToScrollTo = null;

        if (!mIsLaidOut) { // 是否是第一次調用onLayout // If there is a saved state, scroll to the position saved in that state.
            if (mSavedState != null) {
                scrollTo(getScrollX(), mSavedState.scrollPosition);
                mSavedState = null;
            } // mScrollY default value is "0"

            // Make sure current scrollY position falls into the scroll range.  If it doesn't,
            // scroll such that it does.
            int childSize = 0;
            if (getChildCount() > 0) {
                View child = getChildAt(0);
                NestedScrollView.LayoutParams lp = (LayoutParams) child.getLayoutParams();
                childSize = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            }
            int parentSpace = b - t - getPaddingTop() - getPaddingBottom();
            int currentScrollY = getScrollY();
            int newScrollY = clamp(currentScrollY, parentSpace, childSize);
            if (newScrollY != currentScrollY) {
                scrollTo(getScrollX(), newScrollY);
            }
        }

        // Calling this with the present values causes it to re-claim them
        scrollTo(getScrollX(), getScrollY());
        mIsLaidOut = true;
    }

onLayout 方法也沒什麼說的,基本上是用了父類 FrameLayout 的布局方法,加入了一些 scrollTo 操作滑動到指定位置。

嵌套滑動分析

如果對滑動事件不是很清楚的小夥伴可以先看看這篇文章:Android View 的事件分發原理解析。

在分析之前,先做一個假設,比如 RecyclerView 就是 NestedScrollView 的子類,這樣去分析嵌套滑動更容易理解。這時候,用戶點擊 RecyclerView 觸發滑動。需要分析整個滑動過程的事件傳遞。

dispatchTouchEvent

這裏,NestedScrollView 用的是父類的處理,並沒有添加自己的邏輯。

onInterceptTouchEvent

當事件進行分發前,ViewGroup 首先會調用 onInterceptTouchEvent 詢問自己要不要進行攔截,不攔截,就會分發傳遞給子 view。一般來說,對於 ACTION_DOWN 都不會攔截,這樣子類有機會獲取事件,只有子類不處理,才會再次傳給父 View 來處理。下面來看看其具體代碼邏輯:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        /*
         * This method JUST determines whether we want to intercept the motion.
         * If we return true, onMotionEvent will be called and we do the actual
         * scrolling there.
         */

        /*
        * Shortcut the most recurring case: the user is in the dragging
        * state and he is moving his finger.  We want to intercept this
        * motion.
        */
        final int action = ev.getAction();
     // 如果已經在拖動了,說明已經在滑動了,直接返回 true
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { return true; } switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_MOVE: { /* * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check * whether the user has moved far enough from his original down touch. */ /* * Locally do absolute value. mLastMotionY is set to the y value * of the down event. */ final int activePointerId = mActivePointerId; if (activePointerId == INVALID_POINTER) { // If we don't have a valid id, the touch down wasn't on content. 不是一個有效的id break; } final int pointerIndex = ev.findPointerIndex(activePointerId); if (pointerIndex == -1) { Log.e(TAG, "Invalid pointerId=" + activePointerId + " in onInterceptTouchEvent"); break; } final int y = (int) ev.getY(pointerIndex);
          // 計算垂直方向上滑動的距離
final int yDiff = Math.abs(y - mLastMotionY);
          // 確定可以產生滾動了
if (yDiff > mTouchSlop && (getNestedScrollAxes() & ViewCompat.SCROLL_AXIS_VERTICAL) == 0) { mIsBeingDragged = true; mLastMotionY = y; initVelocityTrackerIfNotExists();
            // 可以獲取滑動速率 mVelocityTracker.addMovement(ev); mNestedYOffset
= 0; final ViewParent parent = getParent(); if (parent != null) {
               // 讓父 view 不要攔截,這裏應該是為了保險起見,因為既然已經走進來了,只要你返回 true,父 view 就不會攔截了。 parent.requestDisallowInterceptTouchEvent(
true); } } break; } case MotionEvent.ACTION_DOWN: { final int y = (int) ev.getY();
          // 如果點擊的範圍不在子 view 上,直接break,比如自己設置了很大的 margin,此時用戶點擊這裏,這個範圍理論上是不參与滑動的
if (!inChild((int) ev.getX(), y)) { mIsBeingDragged = false; recycleVelocityTracker(); break; } /* * Remember location of down touch. * ACTION_DOWN always refers to pointer index 0. */ mLastMotionY = y; mActivePointerId = ev.getPointerId(0);           // 在收到 DOWN 事件的時候,做一些初始化的工作 initOrResetVelocityTracker(); mVelocityTracker.addMovement(ev); /* * If being flinged and user touches the screen, initiate drag; * otherwise don't. mScroller.isFinished should be false when * being flinged. We need to call computeScrollOffset() first so that * isFinished() is correct. */ mScroller.computeScrollOffset();
          // 如果此時正在fling, isFinished 會返回 flase mIsBeingDragged
= !mScroller.isFinished();
          // 開始滑動 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: /* Release the drag */ mIsBeingDragged = false; mActivePointerId = INVALID_POINTER; recycleVelocityTracker(); if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { ViewCompat.postInvalidateOnAnimation(this); }
          // 手抬起后,停止滑動 stopNestedScroll(ViewCompat.TYPE_TOUCH);
break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; } /* * The only time we want to intercept motion events is if we are in the * drag mode. */ return mIsBeingDragged; }

onInterceptTouchEvent 事件就是做一件事,決定事件是不是要繼續交給自己的 onTouchEvent 處理。這裏需要注意的一點是,如果子 view 在 dispatchTouchEvent 中調用了:

parent.requestDisallowInterceptTouchEvent(true)

那麼,其實就不會再調用 onInterceptTouchEvent 方法。也就是說上面的邏輯就不會走了。但是可以發現,down 事件,一般是不會攔截的。但是如果正在 fling,此時就會返回 true,直接把事件全部攔截。

那看下 RecyclerView 的 dispatchTouchEvent 是父類的,沒啥好分析的。而且它的 onInterceptTouchEvent 也是做了一些初始化的一些工作,和 NestedScrollView 一樣沒啥可說的。

onTouchEvent

再說 NestedScrollView 的 onTouchEvent。

對於 onTouchEvent 得分兩類進行討論,如果其子 view 不是 ViewGroup ,且是不可點擊的,就會把事件直接交給 NestedScrollView 來處理。

但是如果點擊的子 view 是 RecyclerView 的 ViewGroup 。當 down 事件來的時候,ViewGroup 的子 view 沒有處理,那麼就會交給 ViewGroup 來處理,你會發現ViewGroup 的 onTouchEvent 是默認返回 true 的。也就是說事件都是由  RecyclerView 來處理的。

這時候來看下 NestedScrollView 的 onTouchEvent 代碼:

 public boolean onTouchEvent(MotionEvent ev) {
        initVelocityTrackerIfNotExists();

        MotionEvent vtev = MotionEvent.obtain(ev);

        final int actionMasked = ev.getActionMasked();

        if (actionMasked == MotionEvent.ACTION_DOWN) {
            mNestedYOffset = 0;
        }
        vtev.offsetLocation(0, mNestedYOffset);

        switch (actionMasked) {
            case MotionEvent.ACTION_DOWN: {
          // 需要有一個子類才可以進行滑動
if (getChildCount() == 0) { return false; }
          // 前面提到如果用戶在 fling 的時候,觸碰,此時是直接攔截返回 true,自己來處理事件。
if ((mIsBeingDragged = !mScroller.isFinished())) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } } /* * If being flinged and user touches, stop the fling. isFinished * will be false if being flinged.處理結果就是停止 fling */ if (!mScroller.isFinished()) { mScroller.abortAnimation(); } // Remember where the motion event started mLastMotionY = (int) ev.getY(); mActivePointerId = ev.getPointerId(0);
         // 尋找嵌套父View,告訴它準備在垂直方向上進行 TOUCH 類型的滑動 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
break; } case MotionEvent.ACTION_MOVE: final int activePointerIndex = ev.findPointerIndex(mActivePointerId); if (activePointerIndex == -1) { Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); break; } final int y = (int) ev.getY(activePointerIndex); int deltaY = mLastMotionY - y;
          // 滑動前先把移動距離告訴嵌套父View,看看它要不要消耗,返回 true 代表消耗了部分距離
if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset, ViewCompat.TYPE_TOUCH)) { deltaY -= mScrollConsumed[1]; vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; }
          // 滑動距離大於最大最小觸發距離
if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); }
            // 觸發滑動 mIsBeingDragged
= true; if (deltaY > 0) { deltaY -= mTouchSlop; } else { deltaY += mTouchSlop; } } if (mIsBeingDragged) { // Scroll to follow the motion event mLastMotionY = y - mScrollOffset[1]; final int oldY = getScrollY(); final int range = getScrollRange(); final int overscrollMode = getOverScrollMode(); boolean canOverscroll = overscrollMode == View.OVER_SCROLL_ALWAYS || (overscrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); // Calling overScrollByCompat will call onOverScrolled, which // calls onScrollChanged if applicable.
            // 該方法會觸發自身內容的滾動
if (overScrollByCompat(0, deltaY, 0, getScrollY(), 0, range, 0, 0, true) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)) { // Break our velocity if we hit a scroll barrier. mVelocityTracker.clear(); } final int scrolledDeltaY = getScrollY() - oldY; final int unconsumedY = deltaY - scrolledDeltaY;
            // 通知嵌套的父 View 我已經處理完滾動了,該你來處理了
if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset, ViewCompat.TYPE_TOUCH)) {
              // 如果嵌套父View 消耗了滑動,那麼需要更新 mLastMotionY
-= mScrollOffset[1]; vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; } else if (canOverscroll) { ensureGlows(); final int pulledToY = oldY + deltaY;
               // 觸發邊緣的陰影效果
if (pulledToY < 0) { EdgeEffectCompat.onPull(mEdgeGlowTop, (float) deltaY / getHeight(), ev.getX(activePointerIndex) / getWidth()); if (!mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onRelease(); } } else if (pulledToY > range) { EdgeEffectCompat.onPull(mEdgeGlowBottom, (float) deltaY / getHeight(), 1.f - ev.getX(activePointerIndex) / getWidth()); if (!mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onRelease(); } } if (mEdgeGlowTop != null && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) { ViewCompat.postInvalidateOnAnimation(this); } } } break; case MotionEvent.ACTION_UP: final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
          // 計算滑動速率
int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
          // 大於最小的設定的速率,觸發fling
if ((Math.abs(initialVelocity) > mMinimumVelocity)) { flingWithNestedDispatch(-initialVelocity); } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { ViewCompat.postInvalidateOnAnimation(this); } mActivePointerId = INVALID_POINTER; endDrag(); break; case MotionEvent.ACTION_CANCEL: if (mIsBeingDragged && getChildCount() > 0) { if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { ViewCompat.postInvalidateOnAnimation(this); } } mActivePointerId = INVALID_POINTER; endDrag(); break; case MotionEvent.ACTION_POINTER_DOWN: { final int index = ev.getActionIndex(); mLastMotionY = (int) ev.getY(index); mActivePointerId = ev.getPointerId(index); break; } case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); break; } if (mVelocityTracker != null) { mVelocityTracker.addMovement(vtev); } vtev.recycle(); return true; }

ACTION_DOWN

先看 down 事件,如果處於 fling 期間,那麼直接停止 fling, 接着會調用 startNestedScroll,會讓 NestedScrollView 作為子 view 去 通知嵌套父 view,那麼就需要找到有沒有可以嵌套滑動的父 view 。

    public boolean startNestedScroll(int axes, int type) {
        // 交給 mChildHelper 代理來處理相關邏輯
        return mChildHelper.startNestedScroll(axes, type);
    }


    public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {
        // 找到嵌套父 view 了,就直接返回
        if (hasNestedScrollingParent(type)) {
            // Already in progress
            return true;
        }
        // 是否支持嵌套滾動
        if (isNestedScrollingEnabled()) {
            ViewParent p = mView.getParent();
            View child = mView;
            while (p != null) {  // while 循環,將支持嵌套滑動的父 View 找出來。
                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {
                    // 把父 view 設置進去
                    setNestedScrollingParentForType(type, p);
                    // 找到后,通過該方法可以做一些初始化操作
                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);
                    return true;
                }
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();
            }
        }
        return false;
    }            

可以看到,這時候主要就是為了找到嵌套父 view。當 ViewParentCompat.onStartNestedScroll 返回 true,就表示已經找到嵌套滾動的父 View 了 。下面來看下這個方法的具體邏輯:

    // ViewParentCompat  
    public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
            int nestedScrollAxes, int type) {
        if (parent instanceof NestedScrollingParent2) {
            // First try the NestedScrollingParent2 API
            return ((NestedScrollingParent2) parent).onStartNestedScroll(child, target,
                    nestedScrollAxes, type);
        } else if (type == ViewCompat.TYPE_TOUCH) {
            // Else if the type is the default (touch), try the NestedScrollingParent API
            if (Build.VERSION.SDK_INT >= 21) {
                try {
                    return parent.onStartNestedScroll(child, target, nestedScrollAxes);
                } catch (AbstractMethodError e) {
                    Log.e(TAG, "ViewParent " + parent + " does not implement interface "
                            + "method onStartNestedScroll", e);
                }
            } else if (parent instanceof NestedScrollingParent) {
                return ((NestedScrollingParent) parent).onStartNestedScroll(child, target,
                        nestedScrollAxes);
            }
        }
        return false;
    }

這裏其實沒啥好分析,就是告訴父類當前是什麼類型的滾動,以及滾動方向。其實這裏可以直接看下 NestedScrollView 的 onStartNestedScroll 的邏輯。

//  NestedScrollView
    public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes,
            int type) {
     // 確保觸發的是垂直方向的滾動
return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; }

當確定了嵌套父 View 以後,又會調用父 view 的  onNestedScrollAccepted 方法,在這裏可以做一些準備工作和配置。下面我們看到的 是 Ns 裏面的方法,注意不是父 view 的,只是當作參考。

public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes, int type) {
    mParentHelper.onNestedScrollAccepted(child, target, axes, type);
   // 這裏 Ns 作為子 view 調用 該方法去尋找嵌套父 view。注意這個方法會被調用是 NS 作為父 view 收到的。這樣就 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, type); }

到這裏,down 的作用就講完了。

ACTION_MOVE 

首先是會調用 dispatchNestedPreScroll,講當前的滑動距離告訴嵌套父 View。

  public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow,
            int type) {
     // Ns 作為子 view 去通知父View
return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type); } 

下面看下 mChildHelper 的代碼邏輯:

    /**
     * Dispatch one step of a nested pre-scrolling operation to the current nested scrolling parent.
     *
     * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass
     * method/{@link androidx.core.view.NestedScrollingChild2} interface method with the same
     * signature to implement the standard policy.</p>
     *
     * @return true if the parent consumed any of the nested scroll
     */
    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
            @Nullable int[] offsetInWindow, @NestedScrollType int type) {
        if (isNestedScrollingEnabled()) {
       // 獲取之前找到的嵌套滾動的父 View
final ViewParent parent = getNestedScrollingParentForType(type); if (parent == null) { return false; }        // 滑動距離肯定不為0 才有意義 if (dx != 0 || dy != 0) { int startX = 0; int startY = 0; if (offsetInWindow != null) { mView.getLocationInWindow(offsetInWindow); startX = offsetInWindow[0]; startY = offsetInWindow[1]; } if (consumed == null) { if (mTempNestedScrollConsumed == null) { mTempNestedScrollConsumed = new int[2]; } consumed = mTempNestedScrollConsumed; } consumed[0] = 0; consumed[1] = 0;
          // 調用嵌套父 View 的對應的回調 ViewParentCompat.onNestedPreScroll(parent, mView, dx, dy, consumed, type);
if (offsetInWindow != null) { mView.getLocationInWindow(offsetInWindow); offsetInWindow[0] -= startX; offsetInWindow[1] -= startY; } return consumed[0] != 0 || consumed[1] != 0; } else if (offsetInWindow != null) { offsetInWindow[0] = 0; offsetInWindow[1] = 0; } } return false; }

這裏主要是將滑動距離告訴 父 view,有消耗就會返回 true 。

    // ViewParentCompat
    public static void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
            int[] consumed) {
        onNestedPreScroll(parent, target, dx, dy, consumed, ViewCompat.TYPE_TOUCH);
    }

其實下面的 onNestedPreScroll 跟前面的 onStartNestedScroll 邏輯很像,就是層層傳遞。

    public static void onNestedPreScroll(ViewParent parent, View target, int dx, int dy,
            int[] consumed, int type) {
        if (parent instanceof NestedScrollingParent2) {
            // First try the NestedScrollingParent2 API
            ((NestedScrollingParent2) parent).onNestedPreScroll(target, dx, dy, consumed, type);
        } else if (type == ViewCompat.TYPE_TOUCH) {
            // Else if the type is the default (touch), try the NestedScrollingParent API
            if (Build.VERSION.SDK_INT >= 21) {
                try {
                    parent.onNestedPreScroll(target, dx, dy, consumed);
                } catch (AbstractMethodError e) {
                    Log.e(TAG, "ViewParent " + parent + " does not implement interface "
                            + "method onNestedPreScroll", e);
                }
            } else if (parent instanceof NestedScrollingParent) {
                ((NestedScrollingParent) parent).onNestedPreScroll(target, dx, dy, consumed);
            }
        }
    }

下面為了方便,沒法查看 NS 的嵌套父 View 的邏輯。直接看 Ns 中對應的方法。

    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
            int type) {
     // 最終也是 Ns 再傳給其嵌套父 View dispatchNestedPreScroll(dx, dy, consumed,
null, type); }

傳遞完了之後,就會調用  overScrollByCompat 來實現滾動。

    boolean overScrollByCompat(int deltaX, int deltaY,
            int scrollX, int scrollY,
            int scrollRangeX, int scrollRangeY,
            int maxOverScrollX, int maxOverScrollY,
            boolean isTouchEvent) {
        final int overScrollMode = getOverScrollMode();
        final boolean canScrollHorizontal =
                computeHorizontalScrollRange() > computeHorizontalScrollExtent();
        final boolean canScrollVertical =
                computeVerticalScrollRange() > computeVerticalScrollExtent();
        final boolean overScrollHorizontal = overScrollMode == View.OVER_SCROLL_ALWAYS
                || (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
        final boolean overScrollVertical = overScrollMode == View.OVER_SCROLL_ALWAYS
                || (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);

        int newScrollX = scrollX + deltaX;
        if (!overScrollHorizontal) {
            maxOverScrollX = 0;
        }

        int newScrollY = scrollY + deltaY;
        if (!overScrollVertical) {
            maxOverScrollY = 0;
        }

        // Clamp values if at the limits and record
        final int left = -maxOverScrollX;
        final int right = maxOverScrollX + scrollRangeX;
        final int top = -maxOverScrollY;
        final int bottom = maxOverScrollY + scrollRangeY;

        boolean clampedX = false;
        if (newScrollX > right) {
            newScrollX = right;
            clampedX = true;
        } else if (newScrollX < left) {
            newScrollX = left;
            clampedX = true;
        }

        boolean clampedY = false;
        if (newScrollY > bottom) {
            newScrollY = bottom;
            clampedY = true;
        } else if (newScrollY < top) {
            newScrollY = top;
            clampedY = true;
        }

        if (clampedY && !hasNestedScrollingParent(ViewCompat.TYPE_NON_TOUCH)) {
            mScroller.springBack(newScrollX, newScrollY, 0, 0, 0, getScrollRange());
        }
     
        onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);

        return clampedX || clampedY;
    }

整塊邏輯其實沒啥好說的,然後主要是看 onOverScrolled 這個方法:

   protected void onOverScrolled(int scrollX, int scrollY,
            boolean clampedX, boolean clampedY) {
        super.scrollTo(scrollX, scrollY);
    }

最終是調用 scrollTo 方法來實現了滾動。

當滾動完了后,會調用 dispatchNestedScroll 告訴父 view 當前還剩多少沒消耗,如果是 0,那麼就不會上傳,如果沒消耗完,就會傳給父 View 。

如果是子 View 傳給 NS 的,是會通過 scrollBy 來進行消耗的,然後繼續向上層傳遞。

    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed,
            int dyUnconsumed, int type) {
        final int oldScrollY = getScrollY();
        scrollBy(0, dyUnconsumed);
        final int myConsumed = getScrollY() - oldScrollY;
        final int myUnconsumed = dyUnconsumed - myConsumed;
        dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null,
                type);
    }

假設當前已經滑動到頂部了,此時繼續滑動的話,就會觸發邊緣的陰影效果。

ACTION_UP

當用戶手指離開后,如果滑動速率超過最小的滑動速率,就會調用 flingWithNestedDispatch(-initialVelocity) ,下面來看看這個方法的具體邏輯:

    private void flingWithNestedDispatch(int velocityY) {
        final int scrollY = getScrollY();
        final boolean canFling = (scrollY > 0 || velocityY > 0)
                && (scrollY < getScrollRange() || velocityY < 0);
     // fling 前問問父View 要不要 fling, 一般是返回 false
if (!dispatchNestedPreFling(0, velocityY)) {
       // 這裏主要是告訴父類打算自己消耗了 dispatchNestedFling(
0, velocityY, canFling);
       // 自己處理 fling(velocityY); } }

下面繼續看 fling 的實現。

    public void fling(int velocityY) {
        if (getChildCount() > 0) {

            mScroller.fling(getScrollX(), getScrollY(), // start
                    0, velocityY, // velocities
                    0, 0, // x
                    Integer.MIN_VALUE, Integer.MAX_VALUE, // y
                    0, 0); // overscroll
            runAnimatedScroll(true);
        }
    }

    private void runAnimatedScroll(boolean participateInNestedScrolling) {
        if (participateInNestedScrolling) {
            // fling 其實也是一種滾動,只不過是非接觸的
            startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
        } else {
            stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
        }
        mLastScrollerY = getScrollY();
        ViewCompat.postInvalidateOnAnimation(this);
    }

最終會觸發重繪操作,重繪過程中會調用 computeScroll,下面看下其內部的代碼邏輯。

    @Override
    public void computeScroll() {

        if (mScroller.isFinished()) {
            return;
        }

        mScroller.computeScrollOffset();
        final int y = mScroller.getCurrY();
        int unconsumed = y - mLastScrollerY;
        mLastScrollerY = y;

        // Nested Scrolling Pre Pass
        mScrollConsumed[1] = 0;
     // 滾動的時候,依然會把當前的未消耗的滾動距離傳給嵌套父View dispatchNestedPreScroll(
0, unconsumed, mScrollConsumed, null, ViewCompat.TYPE_NON_TOUCH); unconsumed -= mScrollConsumed[1]; final int range = getScrollRange(); if (unconsumed != 0) { // Internal Scroll final int oldScrollY = getScrollY();
       // 自己消耗 overScrollByCompat(
0, unconsumed, getScrollX(), oldScrollY, 0, range, 0, 0, false); final int scrolledByMe = getScrollY() - oldScrollY; unconsumed -= scrolledByMe; // Nested Scrolling Post Pass mScrollConsumed[1] = 0;
        // 繼續上傳給父View dispatchNestedScroll(
0, scrolledByMe, 0, unconsumed, mScrollOffset, ViewCompat.TYPE_NON_TOUCH, mScrollConsumed); unconsumed -= mScrollConsumed[1]; }      // 如果到這裡有未消耗的,說明已經滾動到邊緣了 if (unconsumed != 0) { final int mode = getOverScrollMode(); final boolean canOverscroll = mode == OVER_SCROLL_ALWAYS || (mode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); if (canOverscroll) { ensureGlows(); if (unconsumed < 0) { if (mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity()); } } else { if (mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity()); } } }
       // 停止滾動   abortAnimatedScroll(); }      // 如果此時滾動還未結束,並且當前的滑動距離都被消耗了,那麼繼續刷新滾動,直到停止為止
if (!mScroller.isFinished()) { ViewCompat.postInvalidateOnAnimation(this); } }

到這裏,關於 Ns 的嵌套滑動就講完了。希望大家能夠對嵌套滑動有個理解。

閱讀 Ns 的源碼,可以讓你更好的理解嵌套滑動,以及事件分發的邏輯。

  本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式